@anthusai/papyrus 1.0.0-next.9 → 1.1.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 (222) hide show
  1. package/amplify/auth/prospectus-google-client.js +66 -0
  2. package/amplify/auth/prospectus-google-client.ts +71 -0
  3. package/amplify/auth/redirect-urls.js +54 -0
  4. package/amplify/auth/redirect-urls.ts +66 -0
  5. package/amplify/auth/resource.js +23 -5
  6. package/amplify/auth/resource.ts +31 -5
  7. package/amplify/data/resource.js +42 -80
  8. package/amplify/data/resource.ts +42 -80
  9. package/amplify/data/schema.js +45 -62
  10. package/amplify/data/schema.ts +47 -62
  11. package/amplify/functions/category-action/handler.ts +5 -3
  12. package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +0 -10
  13. package/amplify/functions/console-chat-responder/resource.js +0 -7
  14. package/amplify/functions/console-chat-responder/resource.ts +0 -8
  15. package/amplify/functions/console-chat-responder/src/main.rs +0 -9
  16. package/amplify/functions/content-actions/handler.py +143 -1
  17. package/amplify/functions/content-actions/requirements.txt +2 -1
  18. package/amplify/functions/content-actions/resource.js +6 -1
  19. package/amplify/functions/content-actions/resource.ts +9 -2
  20. package/amplify/functions/email-submission-processor/handler.py +3 -1
  21. package/amplify/functions/renditions/handler.py +120 -0
  22. package/amplify/functions/renditions/requirements.txt +4 -0
  23. package/amplify/functions/ses-inbound-receive/handler.ts +32 -4
  24. package/amplify/functions/shared/email-submission.ts +109 -1
  25. package/amplify/functions/shared/python-bundle.js +42 -2
  26. package/amplify/functions/shared/python-bundle.ts +47 -2
  27. package/amplify/functions/shared/reference-curation-metadata.ts +29 -0
  28. package/amplify/inbound-email/ses-intake.js +86 -0
  29. package/amplify/inbound-email/ses-intake.ts +110 -0
  30. package/amplify/plugins/registry.js +19 -0
  31. package/amplify/plugins/registry.ts +23 -0
  32. package/amplify/plugins/signup/function/handler.py +200 -0
  33. package/amplify/plugins/signup/function/resource.js +27 -0
  34. package/amplify/plugins/signup/function/resource.ts +35 -0
  35. package/amplify/plugins/signup/papyrus.plugin.js +59 -0
  36. package/amplify/plugins/signup/papyrus.plugin.ts +74 -0
  37. package/amplify/plugins/signup/schema.js +40 -0
  38. package/amplify/plugins/signup/schema.ts +41 -0
  39. package/amplify/plugins/signup/waf.js +48 -0
  40. package/amplify/plugins/signup/waf.ts +51 -0
  41. package/amplify/plugins/types.js +1 -0
  42. package/amplify/plugins/types.ts +39 -0
  43. package/amplify/plugins/wire.js +24 -0
  44. package/amplify/plugins/wire.ts +26 -0
  45. package/amplify/publication/default-publication.js +18 -0
  46. package/amplify/publication/default-publication.ts +20 -0
  47. package/amplify/publication/invalidation-alarm.js +26 -0
  48. package/amplify/publication/invalidation-alarm.ts +30 -0
  49. package/amplify/publication/redirect-and-index.js +37 -0
  50. package/amplify/publication/renditions-function.js +74 -0
  51. package/amplify/publication/renditions-function.ts +112 -0
  52. package/amplify/publication/renditions-settings.js +51 -0
  53. package/amplify/publication/renditions-settings.ts +58 -0
  54. package/amplify/publication/reproject-on-deploy.js +73 -0
  55. package/amplify/publication/reproject-on-deploy.ts +78 -0
  56. package/amplify/publication/s3-projection-settings.js +67 -0
  57. package/amplify/publication/s3-projection-settings.ts +101 -0
  58. package/amplify/publication/s3-projection.js +151 -0
  59. package/amplify/publication/s3-projection.ts +201 -0
  60. package/amplify/resolver-waves.js +26 -0
  61. package/amplify/resolver-waves.ts +34 -0
  62. package/amplify/revalidate-invalidation-settings.js +30 -0
  63. package/amplify/revalidate-invalidation-settings.ts +35 -0
  64. package/amplify/revalidate-secret-policy.js +13 -0
  65. package/amplify/revalidate-secret-policy.ts +19 -0
  66. package/amplify/site-backend-config.js +65 -1
  67. package/amplify/site-backend-config.ts +153 -2
  68. package/amplify/site-backend-names.js +103 -0
  69. package/amplify/site-backend-names.ts +166 -0
  70. package/amplify/site-backend.js +150 -176
  71. package/amplify/site-backend.ts +189 -201
  72. package/amplify/storage-backups/construct.js +31 -0
  73. package/amplify/storage-backups/construct.ts +47 -0
  74. package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +8 -1
  75. package/app/[year]/[month]/[day]/edition-route-page.tsx +2 -0
  76. package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +6 -1
  77. package/app/[year]/[month]/[day]/page.tsx +1 -1
  78. package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +6 -1
  79. package/app/api/archive/editions/route.ts +1 -1
  80. package/app/api/auth/prospectus/callback/route.ts +9 -0
  81. package/app/api/auth/prospectus/selection/route.ts +9 -0
  82. package/app/api/auth/prospectus/session/route.ts +9 -0
  83. package/app/api/auth/prospectus/sign-in/route.ts +9 -0
  84. package/app/api/auth/prospectus/sign-out/route.ts +9 -0
  85. package/app/api/cyclotron/priming-proof/route.ts +11 -0
  86. package/app/api/media/[...path]/route.ts +5 -16
  87. package/app/api/revalidate/route.ts +15 -7
  88. package/app/archive/page.tsx +1 -1
  89. package/app/articles/[slug]/page.tsx +7 -5
  90. package/app/blog-rhythm.css +278 -0
  91. package/app/cyclotron/page.tsx +13 -0
  92. package/app/globals.css +2 -0
  93. package/app/layout.tsx +16 -0
  94. package/app/newsroom/[section]/[...selection]/page.tsx +9 -6
  95. package/app/newsroom/[section]/page.tsx +6 -3
  96. package/app/newsroom/layout.tsx +4 -9
  97. package/app/newsroom/settings/page.tsx +6 -0
  98. package/app/newsroom/work/page.tsx +10 -0
  99. package/app/page.tsx +22 -8
  100. package/app/robots.ts +2 -5
  101. package/app/tailwind.css +1 -6
  102. package/components/amplify-client-provider.tsx +5 -22
  103. package/components/archive-shell.tsx +2 -22
  104. package/components/article-video.tsx +152 -5
  105. package/components/blog-page-background.tsx +23 -6
  106. package/components/news-desk-page.tsx +4 -15
  107. package/components/news-desk-taxonomy-client.ts +90 -3
  108. package/components/newspaper.tsx +6 -2
  109. package/components/newsroom-access-gate.tsx +2 -1
  110. package/components/newsroom-board-view.tsx +99 -0
  111. package/components/newsroom-client-shell.tsx +12 -0
  112. package/components/newsroom-curation-provider.tsx +17 -0
  113. package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
  114. package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
  115. package/components/newsroom-fonts/playfair-display-OFL.txt +93 -0
  116. package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
  117. package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
  118. package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +93 -0
  119. package/components/newsroom-newsprint-board.css +48 -0
  120. package/components/newsroom-ops-overview.tsx +28 -11
  121. package/components/newsroom-ops-shell.tsx +7 -1
  122. package/components/newsroom-organizer-shell.tsx +130 -0
  123. package/components/newsroom-organizer.css +63 -0
  124. package/components/newsroom-references-view.tsx +92 -8
  125. package/components/papyrus-console-shell.tsx +4 -2
  126. package/components/pictogram-figure.tsx +9 -2
  127. package/components/presentation-header.tsx +135 -0
  128. package/components/presentation-rhythm-hrule.tsx +17 -0
  129. package/components/priming-proof-page.tsx +94 -0
  130. package/components/publication-cyclotron-panel.tsx +57 -0
  131. package/components/reader-auth-control.tsx +8 -5
  132. package/components/reference-curation-controls.tsx +77 -0
  133. package/components/reference-cyclotron-status.tsx +109 -0
  134. package/components/reference-priming.tsx +18 -0
  135. package/components/reference-relevance-review.tsx +75 -0
  136. package/components/settings-page.tsx +2 -1
  137. package/components/signup-entries-panel.tsx +94 -0
  138. package/components/topic-steering-workspace.tsx +78 -8
  139. package/components/use-blog-header-obstacles.ts +81 -0
  140. package/components/use-brand-fonts-ready.ts +28 -0
  141. package/components/use-near-viewport.ts +30 -0
  142. package/components/use-rhythm-overlay.ts +29 -0
  143. package/infra/amplify-app-shell.js +166 -27
  144. package/infra/amplify-app-shell.ts +180 -31
  145. package/infra/build-specs.js +23 -7
  146. package/infra/build-specs.ts +27 -10
  147. package/infra/site-config.js +248 -36
  148. package/infra/site-config.ts +313 -51
  149. package/lib/amplify-outputs-path.ts +5 -1
  150. package/lib/amplify-server-runtime.ts +36 -1
  151. package/lib/approved-priming-proof-reports.ts +6 -0
  152. package/lib/blog-feature-solver.ts +459 -0
  153. package/lib/blog-rhythm.ts +134 -0
  154. package/lib/brand-fonts-watch.ts +43 -0
  155. package/lib/brand-fonts.ts +112 -0
  156. package/lib/cached-content-repository.ts +12 -0
  157. package/lib/category-repository.ts +43 -93
  158. package/lib/content-item-lifecycle.ts +55 -0
  159. package/lib/content-repository.ts +3 -0
  160. package/lib/content-types.ts +4 -0
  161. package/lib/curation-client.ts +61 -0
  162. package/lib/edition-routes.ts +31 -29
  163. package/lib/graphql-content-repository.ts +56 -22
  164. package/lib/media-route-response.ts +45 -0
  165. package/lib/newspaper-layout.ts +3 -2
  166. package/lib/newsroom-base-path.ts +12 -3
  167. package/lib/newsroom-board.ts +99 -0
  168. package/lib/newsroom-category-drilldown.ts +3 -2
  169. package/lib/newsroom-forum-routes.ts +6 -4
  170. package/lib/newsroom-index-filters.ts +14 -10
  171. package/lib/newsroom-nav.ts +3 -1
  172. package/lib/newsroom-organizer-nav.ts +37 -0
  173. package/lib/newsroom-section-routing.ts +20 -0
  174. package/lib/oauth-redirect-priority.ts +40 -0
  175. package/lib/papyrus-web-locations.ts +7 -6
  176. package/lib/plugin-settings.ts +11 -0
  177. package/lib/presentation-footer.ts +3 -2
  178. package/lib/pretext-layout.ts +12 -3
  179. package/lib/priming-proof-report.ts +90 -0
  180. package/lib/prospectus-publication-access.ts +53 -0
  181. package/lib/prospectus-session.ts +196 -0
  182. package/lib/prospectus-sign-in-routes.ts +284 -0
  183. package/lib/prospectus-site-identity.ts +303 -0
  184. package/lib/publication-cyclotron.ts +74 -0
  185. package/lib/rate-limit-retry.ts +55 -0
  186. package/lib/reader-base-path.ts +24 -0
  187. package/lib/reader-cache-lifetime.ts +5 -0
  188. package/lib/reader-static-params.ts +85 -4
  189. package/lib/reader-storage-url.ts +9 -12
  190. package/lib/reference-policy.ts +5 -0
  191. package/lib/relevance-decisions.ts +103 -0
  192. package/lib/relevance-status.ts +63 -0
  193. package/lib/renderer.ts +7 -0
  194. package/lib/revalidate-prewarm.ts +53 -0
  195. package/lib/revalidate-secret.ts +59 -0
  196. package/lib/robots-policy.ts +14 -0
  197. package/lib/root-route-redirect.ts +16 -0
  198. package/lib/semantic-graph.ts +7 -0
  199. package/lib/shared-credentials-provider.ts +61 -0
  200. package/lib/signup-csv.ts +24 -0
  201. package/lib/site-brand.ts +59 -0
  202. package/lib/staging-gated-path.ts +23 -0
  203. package/lib/staging-preview-object.ts +21 -5
  204. package/lib/test-editor-override.ts +4 -0
  205. package/lib/video-loading.ts +22 -0
  206. package/lib/video-script.ts +14 -0
  207. package/middleware.ts +11 -19
  208. package/package.json +73 -8
  209. package/renderers/pretext/article-page.tsx +39 -3
  210. package/renderers/pretext/presentation-shared.tsx +113 -0
  211. package/renderers/pretext/presentation-shell.tsx +15 -102
  212. package/renderers/pretext/rhythm-article-page.tsx +165 -0
  213. package/renderers/pretext/rhythm-blog-presentation.tsx +279 -0
  214. package/renderers/pretext/rhythm-shell-style.ts +35 -0
  215. package/routes.manifest.json +123 -12
  216. package/src/sync.mjs +30 -6
  217. package/src/with-papyrus.d.mts +9 -0
  218. package/src/with-papyrus.mjs +48 -3
  219. package/amplify/functions/graphql-jwt-authorizer/handler.ts +0 -193
  220. package/amplify/functions/graphql-jwt-authorizer/resource.js +0 -14
  221. package/amplify/functions/graphql-jwt-authorizer/resource.ts +0 -15
  222. package/components/ai-elements/message.tsx +0 -360
@@ -4,11 +4,14 @@ import { fileURLToPath } from "node:url";
4
4
 
5
5
  /**
6
6
  * Wrap a publication repo's Next config for Papyrus (SPIKE, PPY-82be6c).
7
- * - transpile the package (it ships TypeScript source),
7
+ * - transpile the package and the dependencies that ship TypeScript source (cyclotron),
8
8
  * - alias `papyrus-site` -> ./papyrus.config.ts,
9
9
  * `papyrus-amplify-outputs` -> ./amplify_outputs.json and
10
10
  * `papyrus-site-theme` -> ./publication/theme.css when it exists, else the shipped
11
11
  * empty lib/empty-theme.css (both webpack and turbopack),
12
+ * - trace the corpora/ YAML and amplify_outputs.json that the reader reads via fs at runtime,
13
+ * - cap static generation at 2 workers x 3 pages (override with PAPYRUS_STATIC_GENERATION_WORKERS / _CONCURRENCY) and retry
14
+ * failed pages 3 times, so pre-building the reader pages stays under Cognito guest-credential throttles,
12
15
  * - carry Papyrus's own server-external packages and image settings.
13
16
  */
14
17
  const SERVER_EXTERNAL = [
@@ -22,6 +25,46 @@ const SERVER_EXTERNAL = [
22
25
  "@aws-amplify/data-schema",
23
26
  ];
24
27
 
28
+ /**
29
+ * Next sends `stale-while-revalidate` = `expireTime` - `revalidate` (one year by default), so a shared cache may serve a
30
+ * page for a year past its `s-maxage` while it refreshes in the background. Twice the reader's 60 s page lifetime
31
+ * (lib/reader-cache-lifetime.ts) bounds that to 60 s.
32
+ */
33
+ export const READER_EXPIRE_TIME_SECONDS = 120;
34
+
35
+ export const DEFAULT_STATIC_GENERATION_WORKERS = 2;
36
+ export const DEFAULT_STATIC_GENERATION_CONCURRENCY = 3;
37
+ export const DEFAULT_STATIC_GENERATION_RETRY_COUNT = 3;
38
+
39
+ function positiveIntegerFromEnvironment(name) {
40
+ const parsed = Number.parseInt(process.env[name] ?? "", 10);
41
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
42
+ }
43
+
44
+ export function staticGenerationSettings() {
45
+ return {
46
+ cpus: positiveIntegerFromEnvironment("PAPYRUS_STATIC_GENERATION_WORKERS") ?? DEFAULT_STATIC_GENERATION_WORKERS,
47
+ staticGenerationMaxConcurrency:
48
+ positiveIntegerFromEnvironment("PAPYRUS_STATIC_GENERATION_CONCURRENCY") ?? DEFAULT_STATIC_GENERATION_CONCURRENCY,
49
+ staticGenerationRetryCount: DEFAULT_STATIC_GENERATION_RETRY_COUNT,
50
+ };
51
+ }
52
+
53
+ export const DEFAULT_TRANSPILE_PACKAGES = ["@anthusai/papyrus", "cyclotron"];
54
+
55
+ export function mergeTranspilePackages(existing = []) {
56
+ return [...new Set([...existing, ...DEFAULT_TRANSPILE_PACKAGES])];
57
+ }
58
+
59
+ export const RUNTIME_TRACED_FILES = ["./corpora/**/*", "./amplify_outputs.json"];
60
+
61
+ export function mergeOutputFileTracingIncludes(existing = {}) {
62
+ const merged = { ...existing };
63
+ const current = merged["/**"] ?? [];
64
+ merged["/**"] = [...new Set([...current, ...RUNTIME_TRACED_FILES])];
65
+ return merged;
66
+ }
67
+
25
68
  const EMPTY_THEME = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../lib/empty-theme.css");
26
69
 
27
70
  export function withPapyrus(nextConfig = {}, options = {}) {
@@ -38,11 +81,13 @@ export function withPapyrus(nextConfig = {}, options = {}) {
38
81
  };
39
82
  return {
40
83
  distDir: process.env.NEXT_DIST_DIR || ".next",
84
+ expireTime: READER_EXPIRE_TIME_SECONDS,
41
85
  ...nextConfig,
42
- transpilePackages: [...new Set([...(nextConfig.transpilePackages ?? []), "@anthusai/papyrus"])],
86
+ transpilePackages: mergeTranspilePackages(nextConfig.transpilePackages),
43
87
  serverExternalPackages: [...new Set([...(nextConfig.serverExternalPackages ?? []), ...SERVER_EXTERNAL])],
88
+ outputFileTracingIncludes: mergeOutputFileTracingIncludes(nextConfig.outputFileTracingIncludes),
44
89
  images: nextConfig.images ?? { remotePatterns: [{ protocol: "https", hostname: "**" }] },
45
- experimental: { devtoolSegmentExplorer: false, ...nextConfig.experimental },
90
+ experimental: { devtoolSegmentExplorer: false, ...staticGenerationSettings(), ...nextConfig.experimental },
46
91
  turbopack: {
47
92
  ...nextConfig.turbopack,
48
93
  resolveAlias: {
@@ -1,193 +0,0 @@
1
- import { createHmac, timingSafeEqual } from "node:crypto";
2
- import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
3
-
4
- type AppSyncAuthorizerEvent = {
5
- authorizationToken?: string;
6
- };
7
-
8
- type JwtHeader = {
9
- alg?: string;
10
- typ?: string;
11
- };
12
-
13
- type JwtClaims = {
14
- iss?: string;
15
- sub?: string;
16
- aud?: string | string[];
17
- exp?: number;
18
- nbf?: number;
19
- iat?: number;
20
- scope?: string;
21
- scp?: string | string[];
22
- groups?: string | string[];
23
- roles?: string | string[];
24
- [key: string]: unknown;
25
- };
26
-
27
- export const handler = async (event: AppSyncAuthorizerEvent) => {
28
- try {
29
- const token = normalizeAuthorizationToken(event.authorizationToken);
30
- const jwtSecret = await getJwtSecret();
31
- const claims = verifyHs256Jwt(token, jwtSecret);
32
- assertExpectedIssuer(claims);
33
- assertExpectedAudience(claims);
34
- assertRequiredScope(claims);
35
-
36
- return {
37
- isAuthorized: true,
38
- resolverContext: {
39
- sub: claims.sub ?? "unknown",
40
- iss: claims.iss ?? "unknown",
41
- scope: getClaimValues(claims.scope).join(" "),
42
- },
43
- ttlOverride: 300,
44
- };
45
- } catch (error) {
46
- console.warn(`JWT authorizer rejected request: ${(error as Error).message}`);
47
- return {
48
- isAuthorized: false,
49
- ttlOverride: 0,
50
- };
51
- }
52
- };
53
-
54
- function normalizeAuthorizationToken(value: string | undefined): string {
55
- const token = value
56
- ?.replace(/^PapyrusJwt\s+/i, "")
57
- .replace(/^papyrus-jwt:/i, "")
58
- .replace(/^Bearer\s+/i, "")
59
- .trim();
60
- if (!token) throw new Error("Missing Authorization token.");
61
- return token;
62
- }
63
-
64
- function verifyHs256Jwt(token: string, jwtSecret: string): JwtClaims {
65
- const [encodedHeader, encodedPayload, encodedSignature] = token.split(".");
66
- if (!encodedHeader || !encodedPayload || !encodedSignature) {
67
- throw new Error("JWT must have header, payload, and signature.");
68
- }
69
-
70
- const header = decodeJson<JwtHeader>(encodedHeader);
71
- if (header.alg !== "HS256") {
72
- throw new Error(`Unsupported JWT alg ${header.alg ?? "unknown"}.`);
73
- }
74
-
75
- const claims = decodeJson<JwtClaims>(encodedPayload);
76
- const expectedSignature = createHmac("sha256", jwtSecret)
77
- .update(`${encodedHeader}.${encodedPayload}`)
78
- .digest("base64url");
79
-
80
- if (!constantTimeEqual(encodedSignature, expectedSignature)) {
81
- throw new Error("JWT signature verification failed.");
82
- }
83
-
84
- const now = Math.floor(Date.now() / 1000);
85
- if (typeof claims.exp === "number" && claims.exp <= now) {
86
- throw new Error("JWT is expired.");
87
- }
88
- if (typeof claims.nbf === "number" && claims.nbf > now) {
89
- throw new Error("JWT is not yet valid.");
90
- }
91
-
92
- return claims;
93
- }
94
-
95
- function decodeJson<T>(encoded: string): T {
96
- try {
97
- return JSON.parse(Buffer.from(encoded, "base64url").toString("utf8")) as T;
98
- } catch {
99
- throw new Error("JWT contains invalid base64url JSON.");
100
- }
101
- }
102
-
103
- let cachedJwtSecret: string | null = null;
104
- let ssmClient: SSMClient | null = null;
105
-
106
- async function getJwtSecret(): Promise<string> {
107
- if (cachedJwtSecret) return cachedJwtSecret;
108
-
109
- const directSecret = normalizeOptionalString(process.env.PAPYRUS_JWT_SECRET);
110
- if (directSecret && !isAmplifySecretPlaceholder(directSecret)) {
111
- cachedJwtSecret = directSecret;
112
- return directSecret;
113
- }
114
-
115
- const parameterName = resolveAmplifySsmSecretPath("PAPYRUS_JWT_SECRET");
116
- if (!parameterName) throw new Error("PAPYRUS_JWT_SECRET is not configured.");
117
- ssmClient ??= new SSMClient({});
118
- const response = await ssmClient.send(new GetParameterCommand({
119
- Name: parameterName,
120
- WithDecryption: true,
121
- }));
122
- const secret = normalizeOptionalString(response.Parameter?.Value);
123
- if (!secret) throw new Error(`SSM parameter ${parameterName} did not return a value.`);
124
- cachedJwtSecret = secret;
125
- return secret;
126
- }
127
-
128
- function isAmplifySecretPlaceholder(value: string): boolean {
129
- return /^<.*will be resolved.*>$/i.test(value);
130
- }
131
-
132
- function resolveAmplifySsmSecretPath(name: string): string | null {
133
- const rawConfig = normalizeOptionalString(process.env.AMPLIFY_SSM_ENV_CONFIG);
134
- if (!rawConfig) return null;
135
- try {
136
- const config = JSON.parse(rawConfig) as Record<string, { path?: string; sharedPath?: string } | undefined>;
137
- return normalizeOptionalString(config[name]?.path) ?? normalizeOptionalString(config[name]?.sharedPath);
138
- } catch {
139
- throw new Error("AMPLIFY_SSM_ENV_CONFIG contains invalid JSON.");
140
- }
141
- }
142
-
143
- function normalizeOptionalString(value: unknown): string | null {
144
- if (typeof value !== "string") return null;
145
- const trimmed = value.trim();
146
- return trimmed ? trimmed : null;
147
- }
148
-
149
- function assertExpectedIssuer(claims: JwtClaims): void {
150
- const expectedIssuer = process.env.PAPYRUS_JWT_ISSUER;
151
- if (expectedIssuer && claims.iss !== expectedIssuer) {
152
- throw new Error("JWT issuer is not authorized.");
153
- }
154
- }
155
-
156
- function assertExpectedAudience(claims: JwtClaims): void {
157
- const expectedAudience = process.env.PAPYRUS_JWT_AUDIENCE;
158
- if (!expectedAudience) return;
159
-
160
- const audiences = Array.isArray(claims.aud) ? claims.aud : claims.aud ? [claims.aud] : [];
161
- if (!audiences.includes(expectedAudience)) {
162
- throw new Error("JWT audience is not authorized.");
163
- }
164
- }
165
-
166
- function assertRequiredScope(claims: JwtClaims): void {
167
- const requiredScope = process.env.PAPYRUS_JWT_REQUIRED_SCOPE;
168
- if (!requiredScope) return;
169
-
170
- const values = new Set([
171
- ...getClaimValues(claims.scope),
172
- ...getClaimValues(claims.scp),
173
- ...getClaimValues(claims.groups),
174
- ...getClaimValues(claims.roles),
175
- ]);
176
-
177
- if (!values.has(requiredScope)) {
178
- throw new Error("JWT does not include the required authoring scope.");
179
- }
180
- }
181
-
182
- function getClaimValues(value: unknown): string[] {
183
- if (Array.isArray(value)) return value.flatMap(getClaimValues);
184
- if (typeof value === "string") return value.split(/[,\s]+/).filter(Boolean);
185
- return [];
186
- }
187
-
188
- function constantTimeEqual(left: string, right: string): boolean {
189
- const leftBuffer = Buffer.from(left);
190
- const rightBuffer = Buffer.from(right);
191
- if (leftBuffer.length !== rightBuffer.length) return false;
192
- return timingSafeEqual(leftBuffer, rightBuffer);
193
- }
@@ -1,14 +0,0 @@
1
- import { defineFunction, secret } from "@aws-amplify/backend";
2
- export const graphqlJwtAuthorizer = defineFunction({
3
- name: "papyrus-graphql-jwt-authorizer",
4
- entry: "./handler.ts",
5
- timeoutSeconds: 5,
6
- memoryMB: 256,
7
- resourceGroupName: "data",
8
- environment: {
9
- PAPYRUS_JWT_SECRET: secret("PAPYRUS_JWT_SECRET"),
10
- PAPYRUS_JWT_ISSUER: "papyrus-cli",
11
- PAPYRUS_JWT_AUDIENCE: "papyrus-authoring",
12
- PAPYRUS_JWT_REQUIRED_SCOPE: "papyrus:write",
13
- },
14
- });
@@ -1,15 +0,0 @@
1
- import { defineFunction, secret } from "@aws-amplify/backend";
2
-
3
- export const graphqlJwtAuthorizer = defineFunction({
4
- name: "papyrus-graphql-jwt-authorizer",
5
- entry: "./handler.ts",
6
- timeoutSeconds: 5,
7
- memoryMB: 256,
8
- resourceGroupName: "data",
9
- environment: {
10
- PAPYRUS_JWT_SECRET: secret("PAPYRUS_JWT_SECRET"),
11
- PAPYRUS_JWT_ISSUER: "papyrus-cli",
12
- PAPYRUS_JWT_AUDIENCE: "papyrus-authoring",
13
- PAPYRUS_JWT_REQUIRED_SCOPE: "papyrus:write",
14
- },
15
- });
@@ -1,360 +0,0 @@
1
- "use client";
2
-
3
- import { Button } from "../ui/button";
4
- import {
5
- ButtonGroup,
6
- ButtonGroupText,
7
- } from "../ui/button-group";
8
- import {
9
- Tooltip,
10
- TooltipContent,
11
- TooltipProvider,
12
- TooltipTrigger,
13
- } from "../ui/tooltip";
14
- import { cn } from "../../lib/utils";
15
- import { cjk } from "@streamdown/cjk";
16
- import { code } from "@streamdown/code";
17
- import { math } from "@streamdown/math";
18
- import { mermaid } from "@streamdown/mermaid";
19
- import type { UIMessage } from "ai";
20
- import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
21
- import type { ComponentProps, HTMLAttributes, ReactElement } from "react";
22
- import {
23
- createContext,
24
- memo,
25
- useCallback,
26
- useContext,
27
- useEffect,
28
- useMemo,
29
- useState,
30
- } from "react";
31
- import { Streamdown } from "streamdown";
32
-
33
- export type MessageProps = HTMLAttributes<HTMLDivElement> & {
34
- from: UIMessage["role"];
35
- };
36
-
37
- export const Message = ({ className, from, ...props }: MessageProps) => (
38
- <div
39
- className={cn(
40
- "group flex w-full max-w-[95%] flex-col gap-2",
41
- from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
42
- className
43
- )}
44
- {...props}
45
- />
46
- );
47
-
48
- export type MessageContentProps = HTMLAttributes<HTMLDivElement>;
49
-
50
- export const MessageContent = ({
51
- children,
52
- className,
53
- ...props
54
- }: MessageContentProps) => (
55
- <div
56
- className={cn(
57
- "is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
58
- "group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
59
- "group-[.is-assistant]:text-foreground",
60
- className
61
- )}
62
- {...props}
63
- >
64
- {children}
65
- </div>
66
- );
67
-
68
- export type MessageActionsProps = ComponentProps<"div">;
69
-
70
- export const MessageActions = ({
71
- className,
72
- children,
73
- ...props
74
- }: MessageActionsProps) => (
75
- <div className={cn("flex items-center gap-1", className)} {...props}>
76
- {children}
77
- </div>
78
- );
79
-
80
- export type MessageActionProps = ComponentProps<typeof Button> & {
81
- tooltip?: string;
82
- label?: string;
83
- };
84
-
85
- export const MessageAction = ({
86
- tooltip,
87
- children,
88
- label,
89
- variant = "ghost",
90
- size = "icon-sm",
91
- ...props
92
- }: MessageActionProps) => {
93
- const button = (
94
- <Button size={size} type="button" variant={variant} {...props}>
95
- {children}
96
- <span className="sr-only">{label || tooltip}</span>
97
- </Button>
98
- );
99
-
100
- if (tooltip) {
101
- return (
102
- <TooltipProvider>
103
- <Tooltip>
104
- <TooltipTrigger>{button}</TooltipTrigger>
105
- <TooltipContent>
106
- <p>{tooltip}</p>
107
- </TooltipContent>
108
- </Tooltip>
109
- </TooltipProvider>
110
- );
111
- }
112
-
113
- return button;
114
- };
115
-
116
- interface MessageBranchContextType {
117
- currentBranch: number;
118
- totalBranches: number;
119
- goToPrevious: () => void;
120
- goToNext: () => void;
121
- branches: ReactElement[];
122
- setBranches: (branches: ReactElement[]) => void;
123
- }
124
-
125
- const MessageBranchContext = createContext<MessageBranchContextType | null>(
126
- null
127
- );
128
-
129
- const useMessageBranch = () => {
130
- const context = useContext(MessageBranchContext);
131
-
132
- if (!context) {
133
- throw new Error(
134
- "MessageBranch components must be used within MessageBranch"
135
- );
136
- }
137
-
138
- return context;
139
- };
140
-
141
- export type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {
142
- defaultBranch?: number;
143
- onBranchChange?: (branchIndex: number) => void;
144
- };
145
-
146
- export const MessageBranch = ({
147
- defaultBranch = 0,
148
- onBranchChange,
149
- className,
150
- ...props
151
- }: MessageBranchProps) => {
152
- const [currentBranch, setCurrentBranch] = useState(defaultBranch);
153
- const [branches, setBranches] = useState<ReactElement[]>([]);
154
-
155
- const handleBranchChange = useCallback(
156
- (newBranch: number) => {
157
- setCurrentBranch(newBranch);
158
- onBranchChange?.(newBranch);
159
- },
160
- [onBranchChange]
161
- );
162
-
163
- const goToPrevious = useCallback(() => {
164
- const newBranch =
165
- currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
166
- handleBranchChange(newBranch);
167
- }, [currentBranch, branches.length, handleBranchChange]);
168
-
169
- const goToNext = useCallback(() => {
170
- const newBranch =
171
- currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
172
- handleBranchChange(newBranch);
173
- }, [currentBranch, branches.length, handleBranchChange]);
174
-
175
- const contextValue = useMemo<MessageBranchContextType>(
176
- () => ({
177
- branches,
178
- currentBranch,
179
- goToNext,
180
- goToPrevious,
181
- setBranches,
182
- totalBranches: branches.length,
183
- }),
184
- [branches, currentBranch, goToNext, goToPrevious]
185
- );
186
-
187
- return (
188
- <MessageBranchContext.Provider value={contextValue}>
189
- <div
190
- className={cn("grid w-full gap-2 [&>div]:pb-0", className)}
191
- {...props}
192
- />
193
- </MessageBranchContext.Provider>
194
- );
195
- };
196
-
197
- export type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;
198
-
199
- export const MessageBranchContent = ({
200
- children,
201
- ...props
202
- }: MessageBranchContentProps) => {
203
- const { currentBranch, setBranches, branches } = useMessageBranch();
204
- const childrenArray = useMemo(
205
- () => (Array.isArray(children) ? children : [children]),
206
- [children]
207
- );
208
-
209
- // Use useEffect to update branches when they change
210
- useEffect(() => {
211
- if (branches.length !== childrenArray.length) {
212
- setBranches(childrenArray);
213
- }
214
- }, [childrenArray, branches, setBranches]);
215
-
216
- return childrenArray.map((branch, index) => (
217
- <div
218
- className={cn(
219
- "grid gap-2 overflow-hidden [&>div]:pb-0",
220
- index === currentBranch ? "block" : "hidden"
221
- )}
222
- key={branch.key}
223
- {...props}
224
- >
225
- {branch}
226
- </div>
227
- ));
228
- };
229
-
230
- export type MessageBranchSelectorProps = ComponentProps<typeof ButtonGroup>;
231
-
232
- export const MessageBranchSelector = ({
233
- className,
234
- ...props
235
- }: MessageBranchSelectorProps) => {
236
- const { totalBranches } = useMessageBranch();
237
-
238
- // Don't render if there's only one branch
239
- if (totalBranches <= 1) {
240
- return null;
241
- }
242
-
243
- return (
244
- <ButtonGroup
245
- className={cn(
246
- "[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md",
247
- className
248
- )}
249
- orientation="horizontal"
250
- {...props}
251
- />
252
- );
253
- };
254
-
255
- export type MessageBranchPreviousProps = ComponentProps<typeof Button>;
256
-
257
- export const MessageBranchPrevious = ({
258
- children,
259
- ...props
260
- }: MessageBranchPreviousProps) => {
261
- const { goToPrevious, totalBranches } = useMessageBranch();
262
-
263
- return (
264
- <Button
265
- aria-label="Previous branch"
266
- disabled={totalBranches <= 1}
267
- onClick={goToPrevious}
268
- size="icon-sm"
269
- type="button"
270
- variant="ghost"
271
- {...props}
272
- >
273
- {children ?? <ChevronLeftIcon size={14} />}
274
- </Button>
275
- );
276
- };
277
-
278
- export type MessageBranchNextProps = ComponentProps<typeof Button>;
279
-
280
- export const MessageBranchNext = ({
281
- children,
282
- ...props
283
- }: MessageBranchNextProps) => {
284
- const { goToNext, totalBranches } = useMessageBranch();
285
-
286
- return (
287
- <Button
288
- aria-label="Next branch"
289
- disabled={totalBranches <= 1}
290
- onClick={goToNext}
291
- size="icon-sm"
292
- type="button"
293
- variant="ghost"
294
- {...props}
295
- >
296
- {children ?? <ChevronRightIcon size={14} />}
297
- </Button>
298
- );
299
- };
300
-
301
- export type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;
302
-
303
- export const MessageBranchPage = ({
304
- className,
305
- ...props
306
- }: MessageBranchPageProps) => {
307
- const { currentBranch, totalBranches } = useMessageBranch();
308
-
309
- return (
310
- <ButtonGroupText
311
- className={cn(
312
- "border-none bg-transparent text-muted-foreground shadow-none",
313
- className
314
- )}
315
- {...props}
316
- >
317
- {currentBranch + 1} of {totalBranches}
318
- </ButtonGroupText>
319
- );
320
- };
321
-
322
- export type MessageResponseProps = ComponentProps<typeof Streamdown>;
323
-
324
- const streamdownPlugins = { cjk, code, math, mermaid };
325
-
326
- export const MessageResponse = memo(
327
- ({ className, ...props }: MessageResponseProps) => (
328
- <Streamdown
329
- className={cn(
330
- "size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
331
- className
332
- )}
333
- plugins={streamdownPlugins}
334
- {...props}
335
- />
336
- ),
337
- (prevProps, nextProps) =>
338
- prevProps.children === nextProps.children &&
339
- nextProps.isAnimating === prevProps.isAnimating
340
- );
341
-
342
- MessageResponse.displayName = "MessageResponse";
343
-
344
- export type MessageToolbarProps = ComponentProps<"div">;
345
-
346
- export const MessageToolbar = ({
347
- className,
348
- children,
349
- ...props
350
- }: MessageToolbarProps) => (
351
- <div
352
- className={cn(
353
- "mt-4 flex w-full items-center justify-between gap-4",
354
- className
355
- )}
356
- {...props}
357
- >
358
- {children}
359
- </div>
360
- );