Skip to content

feat: add Research Paper Explainer & Quiz Agent#72

Open
Mortarion002 wants to merge 2 commits intoLamatic:mainfrom
Mortarion002:main
Open

feat: add Research Paper Explainer & Quiz Agent#72
Mortarion002 wants to merge 2 commits intoLamatic:mainfrom
Mortarion002:main

Conversation

@Mortarion002
Copy link

@Mortarion002 Mortarion002 commented Mar 20, 2026

Research Paper Explainer & Quiz Agent

Problem Statement

Reading academic research papers is hard for students and learners
outside the domain. Dense jargon and complex methodology make papers
inaccessible to most people.

What this agent does

  • Paste any research paper abstract or full text
  • Explains it in plain language at 3 levels: Simple / Intermediate / Expert
  • Generates an interactive multiple-choice quiz to test understanding
  • Shows score + explanations after quiz submission

Tech Stack

  • Next.js 14 + TypeScript + Tailwind CSS
  • Two Lamatic Flows: Explain Flow + Quiz Flow
  • Deployable on Vercel

Checklist

  • Forked and starred the repository
  • Unique problem statement (not existing in repo)
  • Lamatic account created
  • Kit follows the kits/ folder structure
  • Demo video (coming soon)
  • New Research Paper Explainer & Quiz Agent kit: Next.js 14 + TypeScript + Tailwind UI that accepts a paper abstract/full text and returns explanations at three levels (Simple, Intermediate, Expert) plus an interactive multiple-choice quiz with scoring and per-question feedback.
  • Two Lamatic Flows included: Explain Flow and Quiz Flow (use Gemini 2.5 Flash model); flows are exportable/deployable and wired via lamatic-config.json.
  • Server logic: API routes /api/explain and /api/quiz; actions/orchestrate.ts implements explainPaper and generateQuiz which call Lamatic flows and poll for results; lib/lamatic-client.ts provides a Lamatic SDK client (env-driven).
  • Frontend: app/page.tsx provides textarea input, level selector, numQuestions slider, explain/quiz tabs, markdown rendering (react-markdown + remark-gfm), loading/error states, and live quiz scoring UI.
  • Tooling/config: Tailwind, PostCSS, Next config (serverActions.bodySizeLimit), TypeScript strict config, package.json, globals.css, and README with setup/deploy instructions.
  • Env & naming updates: uses LAMATIC_PROJECT_ENDPOINT / LAMATIC_PROJECT_ID / LAMATIC_PROJECT_API_KEY (and flow ID envs) with .env.example provided.
  • Commits: integrated Lamatic SDK, replaced raw GraphQL calls, implemented async polling and recursive response extraction, fixed node output mappings, added flows/inputs/meta and updated README.

Copilot AI review requested due to automatic review settings March 20, 2026 09:31
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 20, 2026

📝 Walkthrough

Walkthrough

Adds a new "Research Paper Explainer & Quiz Agent" Next.js kit: UI, styling, API routes, Lamatic client/actions, Lamatic flow configs, docs, and project configuration files enabling explanation and quiz generation from research paper text.

Changes

Cohort / File(s) Summary
Config & Tooling
kits/research-paper-explainer/.env.example, kits/research-paper-explainer/.gitignore, kits/research-paper-explainer/next.config.mjs, kits/research-paper-explainer/postcss.config.mjs, kits/research-paper-explainer/tailwind.config.ts, kits/research-paper-explainer/tsconfig.json, kits/research-paper-explainer/package.json
Added environment template, gitignore, Next.js/Tailwind/PostCSS/TypeScript configs and package.json scripts/dependencies.
Documentation
kits/research-paper-explainer/README.md, kits/research-paper-explainer/flows/*/README.md
Added kit README and per-flow READMEs describing behavior, setup, and contribution instructions.
App Shell & Styling
kits/research-paper-explainer/app/layout.tsx, kits/research-paper-explainer/app/globals.css
Added RootLayout with metadata and font links; global CSS with theme variables, typography, animations, and markdown prose rules.
Main UI
kits/research-paper-explainer/app/page.tsx
Added client-side HomePage component implementing paper input, level selection, explain/quiz tabs, API calls, markdown rendering, quiz interaction and scoring.
API & Orchestration
kits/research-paper-explainer/app/api/explain/route.ts, kits/research-paper-explainer/app/api/quiz/route.ts, kits/research-paper-explainer/actions/orchestrate.ts, kits/research-paper-explainer/lib/lamatic-client.ts
Added two POST API endpoints with input validation and error handling; Lamatic client singleton and orchestration functions (explainPaper, generateQuiz) that call/ poll Lamatic flows and normalize outputs (includes JSON extraction/parsing for quiz responses).
Lamatic Agent Config
kits/research-paper-explainer/lamatic-config.json
Added agent metadata declaring two required flow steps (explain-flow, quiz-flow) and integrations/features.
Flows: explain-flow
kits/research-paper-explainer/flows/explain-flow/config.json, .../inputs.json, .../meta.json
Added flow definition, private model input config, and metadata for the explain flow (Gemini model used in node config).
Flows: quiz-flow
kits/research-paper-explainer/flows/quiz-flow/config.json, .../inputs.json, .../meta.json
Added flow definition enforcing JSON quiz output, private model input config, and metadata for the quiz flow.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Page as app/page.tsx
    participant API as /api/explain
    participant Orch as orchestrate.ts
    participant Lamatic as Lamatic Flow
    User->>Page: Provide paperContent + level
    Page->>API: POST { paperContent, level }
    API->>Orch: explainPaper(paperContent, level)
    Orch->>Lamatic: executeFlow(flowId, inputs)
    Lamatic-->>Orch: initialResponse (maybe requestId)
    Orch->>Lamatic: checkStatus(requestId) [poll]
    Lamatic-->>Orch: final generatedResponse
    Orch-->>API: { success, data: explanation }
    API-->>Page: { explanation }
    Page->>User: Render explanation (markdown)
Loading
sequenceDiagram
    actor User
    participant Page as app/page.tsx
    participant API as /api/quiz
    participant Orch as orchestrate.ts
    participant Lamatic as Lamatic Flow
    User->>Page: Provide paperContent + numQuestions
    Page->>API: POST { paperContent, numQuestions }
    API->>Orch: generateQuiz(paperContent, numQuestions)
    Orch->>Lamatic: executeFlow(quizFlowId, inputs)
    Lamatic-->>Orch: initialResponse (maybe requestId)
    Orch->>Lamatic: checkStatus(requestId) [poll]
    Lamatic-->>Orch: generatedResponse (string or JSON)
    Orch->>Orch: extract/parse JSON if needed
    Orch-->>API: { success, data: quizObject }
    API-->>Page: quizObject
    Page->>User: Render interactive quiz, accept answers, compute score
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a new Research Paper Explainer & Quiz Agent kit with specific features.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

CodeRabbit can approve the review once all CodeRabbit's comments are resolved.

Enable the reviews.request_changes_workflow setting to automatically approve the review once all CodeRabbit's comments are resolved.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new Next.js-based kit under kits/research-paper-explainer/ that connects to two Lamatic flows to (1) explain pasted research paper text at different difficulty levels and (2) generate a multiple-choice quiz with scoring.

Changes:

  • New Next.js 14 + Tailwind UI for paper explanation + quiz-taking.
  • New API routes that proxy requests to Lamatic flow endpoints for explain/quiz.
  • New kit packaging/docs/config (Lamatic config, env example, build tooling).

Reviewed changes

Copilot reviewed 14 out of 15 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
kits/research-paper-explainer/tsconfig.json TypeScript config for the kit’s Next.js app.
kits/research-paper-explainer/tailwind.config.ts Tailwind content scanning + theme scaffold.
kits/research-paper-explainer/postcss.config.mjs PostCSS setup for Tailwind + autoprefixer.
kits/research-paper-explainer/package.json Next.js/React deps and scripts for the kit.
kits/research-paper-explainer/next.config.mjs Next.js config placeholder.
kits/research-paper-explainer/lamatic-config.json Declares the two Lamatic flows (explain + quiz).
kits/research-paper-explainer/app/page.tsx Client UI for input, explanation rendering, quiz UX + scoring.
kits/research-paper-explainer/app/layout.tsx Root layout + Google Fonts wiring + metadata.
kits/research-paper-explainer/app/globals.css Global styles, design tokens, and animations.
kits/research-paper-explainer/app/api/explain/route.ts Server route that calls the Lamatic explain flow.
kits/research-paper-explainer/app/api/quiz/route.ts Server route that calls the Lamatic quiz flow + parses JSON.
kits/research-paper-explainer/README.md Kit documentation + setup + expected quiz JSON shape.
kits/research-paper-explainer/.gitignore Kit-local ignores for Next/Vercel/node artifacts.
kits/research-paper-explainer/.env.example Example env vars for Lamatic + flow URLs.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
],
"env": {
"required": ["LAMATIC_API_KEY", "LAMATIC_PROJECT_ID"],
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

env.required lists LAMATIC_PROJECT_ID, but this kit's server routes never read it; conversely, the app does require EXPLAIN_FLOW_URL and QUIZ_FLOW_URL (used in the API routes) but they aren’t declared here. Please align this env list with what the kit actually uses (either add the flow URL vars as required and/or remove/justify LAMATIC_PROJECT_ID).

Suggested change
"required": ["LAMATIC_API_KEY", "LAMATIC_PROJECT_ID"],
"required": ["LAMATIC_API_KEY", "EXPLAIN_FLOW_URL", "QUIZ_FLOW_URL"],

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,68 @@
{
"name": "Research Paper Explainer & Quiz Agent",
"description": "An AI agent that takes any research paper (URL or abstract), explains it in plain language at multiple levels, and generates an interactive quiz to test understanding.",
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config description claims input can be a research paper URL, but the frontend only accepts raw text input and the backend does not fetch or parse URLs. To avoid misleading users, either add URL support end-to-end or remove the URL claim from the description.

Suggested change
"description": "An AI agent that takes any research paper (URL or abstract), explains it in plain language at multiple levels, and generates an interactive quiz to test understanding.",
"description": "An AI agent that takes any research paper text (such as an abstract or full paper), explains it in plain language at multiple levels, and generates an interactive quiz to test understanding.",

Copilot uses AI. Check for mistakes.
Comment on lines +214 to +229
{loading && tab === "explain" ? "Explaining…" : "✦ Explain This Paper"}
</button>
<button
onClick={handleQuiz}
disabled={loading || paperContent.length < 50}
className="w-full py-3.5 rounded-xl text-sm font-semibold transition-all duration-200"
style={{
background: "#fff",
color: "var(--ink)",
border: "1.5px solid var(--border)",
cursor: loading || paperContent.length < 50 ? "not-allowed" : "pointer",
opacity: paperContent.length < 50 ? 0.5 : 1,
}}
>
{loading && tab === "quiz" ? "Generating Quiz…" : "◈ Generate Quiz"}
</button>
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loading is global, but the button labels depend on tab (which only changes after the request finishes). If you click “Generate Quiz” while on the Explanation tab, the quiz button won’t show “Generating Quiz…” during the request (and vice-versa for Explain). Track the in-flight action separately (e.g., loadingAction: 'explain' | 'quiz' | null) or set the tab/action before starting the fetch so the correct label is shown.

Copilot uses AI. Check for mistakes.
Comment on lines +75 to +77
setQuiz({
questions: data.questions,
answers: new Array(data.questions.length).fill(null),
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleQuiz() assumes the response always contains a valid questions array; if Lamatic returns malformed JSON or an unexpected shape, data.questions.length will throw and break the page. Add runtime validation before using data.questions (and surface a friendly error) to prevent a hard crash.

Suggested change
setQuiz({
questions: data.questions,
answers: new Array(data.questions.length).fill(null),
const questions = Array.isArray(data?.questions) ? data.questions : null;
if (!questions || questions.length === 0) {
throw new Error("Received invalid quiz data. Please try again.");
}
const hasValidShape = questions.every(
(q: any) =>
q &&
typeof q.question === "string" &&
Array.isArray(q.options) &&
typeof q.correct === "number" &&
typeof q.explanation === "string"
);
if (!hasValidShape) {
throw new Error("Received malformed quiz questions. Please try again.");
}
setQuiz({
questions,
answers: new Array(questions.length).fill(null),

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +57
const data = await response.json();
const raw = data?.result || data?.output || data;

// Parse if the LLM returned a JSON string
let parsed: { questions: QuizQuestion[] };
if (typeof raw === "string") {
const match = raw.match(/\{[\s\S]*\}/);
parsed = JSON.parse(match ? match[0] : raw);
} else {
parsed = raw;
}

return NextResponse.json(parsed);
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The quiz endpoint can return invalid data to the client: parsed is assumed to be { questions: QuizQuestion[] } without validating that raw actually has a questions array (and JSON.parse failures fall through to the generic 500). Please add explicit parsing/validation (e.g., try/catch around JSON.parse, ensure parsed.questions is an array with the expected fields, and return a 502 with a clear error when Lamatic returns malformed output) so the UI doesn’t crash on unexpected responses.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +33
const { paperContent, numQuestions } = await req.json();

if (!paperContent || paperContent.trim().length < 50) {
return NextResponse.json(
{ error: "Please provide paper content before generating a quiz." },
{ status: 400 }
);
}

const flowUrl = process.env.QUIZ_FLOW_URL;
const apiKey = process.env.LAMATIC_API_KEY;

if (!flowUrl || !apiKey) {
return NextResponse.json(
{ error: "Server misconfiguration: missing QUIZ_FLOW_URL or LAMATIC_API_KEY." },
{ status: 500 }
);
}

const response = await fetch(flowUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
paperContent,
numQuestions: numQuestions || 5,
}),
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

numQuestions is forwarded to the Lamatic flow without type/range validation. Since request JSON can provide a string/float/negative/very large value, consider coercing to a number and clamping to the supported UI range (3–10) before calling the flow (and returning 400 on invalid input).

Copilot uses AI. Check for mistakes.

> A Next.js kit built on [Lamatic AgentKit](https://github.com/Lamatic/AgentKit) that takes any research paper abstract or full text, explains it at your chosen comprehension level, and generates an interactive multiple-choice quiz to test understanding.

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/Mortarion002/AgentKit/tree/main/apps/research-paper-explainer)
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Vercel deploy button URL points to a personal fork and to /apps/research-paper-explainer, but this kit lives under kits/research-paper-explainer in the main Lamatic/AgentKit repo. This link will fail or deploy the wrong directory; update it to use repository-url=https://github.com/Lamatic/AgentKit plus root-directory=kits/research-paper-explainer (and ideally include required env vars like other kits do).

Suggested change
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/Mortarion002/AgentKit/tree/main/apps/research-paper-explainer)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/Lamatic/AgentKit&root-directory=kits/research-paper-explainer)

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,155 @@
# 📚 Research Paper Explainer & Quiz Agent

> A Next.js kit built on [Lamatic AgentKit](https://github.com/Lamatic/AgentKit) that takes any research paper abstract or full text, explains it at your chosen comprehension level, and generates an interactive multiple-choice quiz to test understanding.
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence says the kit accepts a research paper "(URL or abstract)", but the UI currently only supports pasting text into a textarea (no URL fetching/parsing). Either implement URL ingestion or adjust the docs to match the actual input method.

Suggested change
> A Next.js kit built on [Lamatic AgentKit](https://github.com/Lamatic/AgentKit) that takes any research paper abstract or full text, explains it at your chosen comprehension level, and generates an interactive multiple-choice quiz to test understanding.
> A Next.js kit built on [Lamatic AgentKit](https://github.com/Lamatic/AgentKit) that lets you paste any research paper abstract or full text into the app, explains it at your chosen comprehension level, and generates an interactive multiple-choice quiz to test understanding.

Copilot uses AI. Check for mistakes.
Comment on lines +40 to +67
async function handleExplain() {
if (!paperContent.trim()) return;
setLoading(true);
setError(null);
setExplanation(null);
try {
const res = await fetch("/api/explain", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ paperContent, level }),
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Something went wrong.");
setExplanation(typeof data.explanation === "string" ? data.explanation : JSON.stringify(data.explanation, null, 2));
setTab("explain");
} catch (e: unknown) {
setError(e instanceof Error ? e.message : "Unknown error");
} finally {
setLoading(false);
}
}

async function handleQuiz() {
if (!paperContent.trim()) return;
setLoading(true);
setError(null);
setQuiz(null);
try {
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UI and API enforce a 50-character minimum, but handleExplain() / handleQuiz() only early-return on empty input (not < 50). This creates inconsistent behavior (e.g., programmatic calls still fire a request and then show a server error). Consider checking paperContent.trim().length < 50 on the client too and setting a user-facing error before sending the request.

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +31
const { paperContent, level } = await req.json();

if (!paperContent || paperContent.trim().length < 50) {
return NextResponse.json(
{ error: "Please provide a research paper abstract or content (at least 50 characters)." },
{ status: 400 }
);
}

const flowUrl = process.env.EXPLAIN_FLOW_URL;
const apiKey = process.env.LAMATIC_API_KEY;

if (!flowUrl || !apiKey) {
return NextResponse.json(
{ error: "Server misconfiguration: missing EXPLAIN_FLOW_URL or LAMATIC_API_KEY." },
{ status: 500 }
);
}

const response = await fetch(flowUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({ paperContent, level: level || "undergraduate" }),
});
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

level is passed through to the Lamatic flow without validation. To avoid unexpected prompts/behavior, validate that level is one of the supported values (high-school/undergraduate/expert) and return 400 (or default safely) when it isn’t.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🧹 Nitpick comments (3)
kits/research-paper-explainer/.env.example (1)

5-5: Clarify or remove LAMATIC_PROJECT_ID from runtime template.

At Line 5, this variable is declared for setup, but kits/research-paper-explainer/app/api/explain/route.ts (Line 14-15) and kits/research-paper-explainer/app/api/quiz/route.ts (Line 14-15) don’t consume it. Consider documenting why it is required to reduce setup friction.

kits/research-paper-explainer/app/api/quiz/route.ts (1)

24-34: Consider adding a timeout to the external fetch call.

The fetch to flowUrl has no timeout configured. If the Lamatic service is slow or unresponsive, this request will hang indefinitely, potentially causing connection pool exhaustion or poor user experience.

Suggested fix using AbortController
+    const controller = new AbortController();
+    const timeoutId = setTimeout(() => controller.abort(), 30000);
+
     const response = await fetch(flowUrl, {
       method: "POST",
       headers: {
         "Content-Type": "application/json",
         Authorization: `Bearer ${apiKey}`,
       },
       body: JSON.stringify({
         paperContent,
         numQuestions: numQuestions || 5,
       }),
+      signal: controller.signal,
     });
+
+    clearTimeout(timeoutId);
kits/research-paper-explainer/app/globals.css (1)

61-76: Consider using kebab-case for keyframe names.

For consistency with CSS naming conventions, keyframe names like fadeIn could be fade-in. This is a minor style preference flagged by Stylelint.

Optional fix
-@keyframes fadeIn {
+@keyframes fade-in {
   from { opacity: 0; transform: translateY(12px); }
   to { opacity: 1; transform: translateY(0); }
 }

 .animate-fade-in {
-  animation: fadeIn 0.45s ease-out forwards;
+  animation: fade-in 0.45s ease-out forwards;
 }
 .animate-fade-in-delay {
-  animation: fadeIn 0.45s ease-out 0.15s forwards;
+  animation: fade-in 0.45s ease-out 0.15s forwards;
   opacity: 0;
 }

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e93065c5-6722-432d-8ec8-6a4562772468

📥 Commits

Reviewing files that changed from the base of the PR and between 994954e and dac96c2.

⛔ Files ignored due to path filters (1)
  • kits/research-paper-explainer/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (14)
  • kits/research-paper-explainer/.env.example
  • kits/research-paper-explainer/.gitignore
  • kits/research-paper-explainer/README.md
  • kits/research-paper-explainer/app/api/explain/route.ts
  • kits/research-paper-explainer/app/api/quiz/route.ts
  • kits/research-paper-explainer/app/globals.css
  • kits/research-paper-explainer/app/layout.tsx
  • kits/research-paper-explainer/app/page.tsx
  • kits/research-paper-explainer/lamatic-config.json
  • kits/research-paper-explainer/next.config.mjs
  • kits/research-paper-explainer/package.json
  • kits/research-paper-explainer/postcss.config.mjs
  • kits/research-paper-explainer/tailwind.config.ts
  • kits/research-paper-explainer/tsconfig.json

QUIZ_FLOW_URL=https://your-project.lamatic.app/api/quiz

# Optional: Customize the app title
NEXT_PUBLIC_APP_TITLE=Research Paper Explainer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify unquoted env values containing whitespace in .env example files.
rg -nP '^[A-Z0-9_]+=.*\s+.*$' --glob '**/.env.example' -C1

Repository: Lamatic/AgentKit

Length of output: 892


Quote env values that contain spaces.

At line 12, the unquoted value can cause dotenv tooling inconsistencies; quote it for portability and consistency with similar values elsewhere in the codebase.

Suggested patch
-NEXT_PUBLIC_APP_TITLE=Research Paper Explainer
+NEXT_PUBLIC_APP_TITLE="Research Paper Explainer"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NEXT_PUBLIC_APP_TITLE=Research Paper Explainer
NEXT_PUBLIC_APP_TITLE="Research Paper Explainer"
🧰 Tools
🪛 dotenv-linter (4.0.0)

[warning] 12-12: [ValueWithoutQuotes] This value needs to be surrounded in quotes

(ValueWithoutQuotes)

Comment on lines +5 to +12
const { paperContent, level } = await req.json();

if (!paperContent || paperContent.trim().length < 50) {
return NextResponse.json(
{ error: "Please provide a research paper abstract or content (at least 50 characters)." },
{ status: 400 }
);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard input types before calling .trim().

At Line 7, non-string paperContent (e.g., object/null) will throw and end up as a 500. Validate type and return 400 for invalid payloads.

Suggested patch
-    const { paperContent, level } = await req.json();
+    const body = await req.json();
+    const paperContent = typeof body?.paperContent === "string" ? body.paperContent : "";
+    const level = typeof body?.level === "string" ? body.level : undefined;
 
-    if (!paperContent || paperContent.trim().length < 50) {
+    if (!paperContent || paperContent.trim().length < 50) {
       return NextResponse.json(
         { error: "Please provide a research paper abstract or content (at least 50 characters)." },
         { status: 400 }
       );
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { paperContent, level } = await req.json();
if (!paperContent || paperContent.trim().length < 50) {
return NextResponse.json(
{ error: "Please provide a research paper abstract or content (at least 50 characters)." },
{ status: 400 }
);
}
const body = await req.json();
const paperContent = typeof body?.paperContent === "string" ? body.paperContent : "";
const level = typeof body?.level === "string" ? body.level : undefined;
if (!paperContent || paperContent.trim().length < 50) {
return NextResponse.json(
{ error: "Please provide a research paper abstract or content (at least 50 characters)." },
{ status: 400 }
);
}

Comment on lines +24 to +31
const response = await fetch(flowUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({ paperContent, level: level || "undergraduate" }),
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add a timeout to the Lamatic upstream call.

At Line 24, the external fetch has no timeout/abort path. A slow upstream can stall this route and degrade availability.

Suggested patch
-    const response = await fetch(flowUrl, {
+    const controller = new AbortController();
+    const timeout = setTimeout(() => controller.abort(), 15000);
+    const response = await fetch(flowUrl, {
       method: "POST",
       headers: {
         "Content-Type": "application/json",
         Authorization: `Bearer ${apiKey}`,
       },
       body: JSON.stringify({ paperContent, level: level || "undergraduate" }),
+      signal: controller.signal,
     });
+    clearTimeout(timeout);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const response = await fetch(flowUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({ paperContent, level: level || "undergraduate" }),
});
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 15000);
const response = await fetch(flowUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({ paperContent, level: level || "undergraduate" }),
signal: controller.signal,
});
clearTimeout(timeout);

Comment on lines +48 to +55
// Parse if the LLM returned a JSON string
let parsed: { questions: QuizQuestion[] };
if (typeof raw === "string") {
const match = raw.match(/\{[\s\S]*\}/);
parsed = JSON.parse(match ? match[0] : raw);
} else {
parsed = raw;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate parsed quiz structure before returning.

The QuizQuestion interface is defined but never used for runtime validation. If the LLM returns malformed JSON (e.g., missing questions array, wrong types), the client will crash. Consider adding basic structure validation.

Suggested validation
     let parsed: { questions: QuizQuestion[] };
     if (typeof raw === "string") {
       const match = raw.match(/\{[\s\S]*\}/);
       parsed = JSON.parse(match ? match[0] : raw);
     } else {
       parsed = raw;
     }

+    if (!Array.isArray(parsed?.questions) || parsed.questions.length === 0) {
+      console.error("Invalid quiz structure:", parsed);
+      return NextResponse.json(
+        { error: "The AI returned an invalid quiz format. Please try again." },
+        { status: 502 }
+      );
+    }
+
     return NextResponse.json(parsed);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Parse if the LLM returned a JSON string
let parsed: { questions: QuizQuestion[] };
if (typeof raw === "string") {
const match = raw.match(/\{[\s\S]*\}/);
parsed = JSON.parse(match ? match[0] : raw);
} else {
parsed = raw;
}
// Parse if the LLM returned a JSON string
let parsed: { questions: QuizQuestion[] };
if (typeof raw === "string") {
const match = raw.match(/\{[\s\S]*\}/);
parsed = JSON.parse(match ? match[0] : raw);
} else {
parsed = raw;
}
if (!Array.isArray(parsed?.questions) || parsed.questions.length === 0) {
console.error("Invalid quiz structure:", parsed);
return NextResponse.json(
{ error: "The AI returned an invalid quiz format. Please try again." },
{ status: 502 }
);
}
return NextResponse.json(parsed);

Comment on lines +73 to +79
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Something went wrong.");
setQuiz({
questions: data.questions,
answers: new Array(data.questions.length).fill(null),
submitted: false,
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add defensive validation for quiz API response.

The code assumes data.questions exists and is an array. If the API returns an unexpected shape (e.g., questions is undefined or malformed), calling .length will throw a runtime error.

Suggested fix
       const data = await res.json();
       if (!res.ok) throw new Error(data.error || "Something went wrong.");
+      if (!Array.isArray(data.questions)) {
+        throw new Error("Invalid quiz response format.");
+      }
       setQuiz({
         questions: data.questions,
         answers: new Array(data.questions.length).fill(null),
         submitted: false,
       });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Something went wrong.");
setQuiz({
questions: data.questions,
answers: new Array(data.questions.length).fill(null),
submitted: false,
});
const data = await res.json();
if (!res.ok) throw new Error(data.error || "Something went wrong.");
if (!Array.isArray(data.questions)) {
throw new Error("Invalid quiz response format.");
}
setQuiz({
questions: data.questions,
answers: new Array(data.questions.length).fill(null),
submitted: false,
});

Comment on lines +32 to +47
```
research-paper-explainer/
├── app/
│ ├── page.tsx # Main UI (input + output panels)
│ ├── layout.tsx # Root layout with fonts
│ ├── globals.css # Design system & animations
│ └── api/
│ ├── explain/route.ts # Calls the Lamatic explain flow
│ └── quiz/route.ts # Calls the Lamatic quiz flow
├── lamatic-config.json # Flow definitions for Lamatic Studio
├── .env.example # Required environment variables
├── package.json
├── tailwind.config.ts
├── tsconfig.json
└── README.md
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language specifier to fenced code block.

The folder structure code block is missing a language specifier. While this doesn't affect rendering, it helps with accessibility and linting compliance.

Suggested fix
-```
+```text
 research-paper-explainer/
 ├── app/
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```
research-paper-explainer/
├── app/
│ ├── page.tsx # Main UI (input + output panels)
│ ├── layout.tsx # Root layout with fonts
│ ├── globals.css # Design system & animations
│ └── api/
│ ├── explain/route.ts # Calls the Lamatic explain flow
│ └── quiz/route.ts # Calls the Lamatic quiz flow
├── lamatic-config.json # Flow definitions for Lamatic Studio
├── .env.example # Required environment variables
├── package.json
├── tailwind.config.ts
├── tsconfig.json
└── README.md
```
🧰 Tools
🪛 markdownlint-cli2 (0.21.0)

[warning] 32-32: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

- Built two Lamatic flows: explain-flow and quiz-flow
- Configured API Request trigger with paperContent + level schema
- Added Generate Text node with Gemini 2.5 Flash (switched from Groq after credential issues)
- Fixed API Response output mapping to use LLMNode.output.generatedResponse
- Recreated Generate Text node to resolve invalid node reference error
- Integrated Lamatic SDK (npm i lamatic) replacing raw GraphQL fetch calls
- Fixed env variables to match Lamatic SDK format (LAMATIC_PROJECT_ENDPOINT, LAMATIC_PROJECT_API_KEY)
- Implemented async polling via lamaticClient.checkStatus() for flow responses
- Added recursive text extraction to handle nested Lamatic response structure
- Created actions/orchestrate.ts and lib/lamatic-client.ts following official sample kit pattern
- Built Next.js 14 frontend with explain + quiz panels
- Added 3-level explanation (Simple / Intermediate / Expert)
- Added interactive MCQ quiz with live scoring and answer explanations
- Exported flows to flows/ folder
- Added config.json with kit metadata
- Updated README with c
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 15648233-3f97-4f9c-8673-2d0045af110b

📥 Commits

Reviewing files that changed from the base of the PR and between dac96c2 and 10a7e29.

⛔ Files ignored due to path filters (1)
  • kits/research-paper-explainer/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (16)
  • kits/research-paper-explainer/README.md
  • kits/research-paper-explainer/actions/orchestrate.ts
  • kits/research-paper-explainer/app/api/explain/route.ts
  • kits/research-paper-explainer/app/api/quiz/route.ts
  • kits/research-paper-explainer/flows/explain-flow/README.md
  • kits/research-paper-explainer/flows/explain-flow/config.json
  • kits/research-paper-explainer/flows/explain-flow/inputs.json
  • kits/research-paper-explainer/flows/explain-flow/meta.json
  • kits/research-paper-explainer/flows/quiz-flow/README.md
  • kits/research-paper-explainer/flows/quiz-flow/config.json
  • kits/research-paper-explainer/flows/quiz-flow/inputs.json
  • kits/research-paper-explainer/flows/quiz-flow/meta.json
  • kits/research-paper-explainer/lamatic-config.json
  • kits/research-paper-explainer/lib/lamatic-client.ts
  • kits/research-paper-explainer/next.config.mjs
  • kits/research-paper-explainer/package.json
✅ Files skipped from review due to trivial changes (5)
  • kits/research-paper-explainer/flows/quiz-flow/meta.json
  • kits/research-paper-explainer/flows/explain-flow/inputs.json
  • kits/research-paper-explainer/flows/quiz-flow/inputs.json
  • kits/research-paper-explainer/package.json
  • kits/research-paper-explainer/flows/explain-flow/meta.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • kits/research-paper-explainer/app/api/quiz/route.ts
  • kits/research-paper-explainer/app/api/explain/route.ts
  • kits/research-paper-explainer/lamatic-config.json

Comment on lines +5 to +6
const EXPLAIN_FLOW_ID = process.env.LAMATIC_FLOW_ID ?? ""
const QUIZ_FLOW_ID = process.env.QUIZ_FLOW_ID ?? ""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject missing flow IDs before calling Lamatic.

?? "" lets a misconfigured deployment reach executeFlow(""), which turns a setup problem into an opaque upstream failure on the first request. Short-circuit with a clear configuration error before the SDK call.

Suggested fix
 export async function explainPaper(
   paperContent: string,
   level: string
 ): Promise<{ success: boolean; data?: string; error?: string }> {
   try {
+    if (!EXPLAIN_FLOW_ID) {
+      return { success: false, error: "LAMATIC_FLOW_ID is not configured" };
+    }
+
     console.log("[explain] Calling flow:", EXPLAIN_FLOW_ID)
@@
 export async function generateQuiz(
   paperContent: string,
   numQuestions: number
 ): Promise<{ success: boolean; data?: any; error?: string }> {
   try {
+    if (!QUIZ_FLOW_ID) {
+      return { success: false, error: "QUIZ_FLOW_ID is not configured" };
+    }
+
     console.log("[quiz] Calling flow:", QUIZ_FLOW_ID)

Also applies to: 12-19, 61-67

Comment on lines +21 to +29
console.log("[explain] Initial response:", JSON.stringify(resData))

// Step 2 - if async, poll using checkStatus
const requestId = resData?.result?.requestId || resData?.requestId
if (requestId) {
console.log("[explain] Polling for requestId:", requestId)
// poll every 3 seconds, timeout after 60 seconds
const pollData = await lamaticClient.checkStatus(requestId, 3, 60) as any
console.log("[explain] Poll result:", JSON.stringify(pollData))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Redact Lamatic payloads from logs and error strings.

These JSON.stringify(...) calls can capture the submitted paper text and generated explanations/quizzes. The explain path also bakes the raw payload into error.message, which is later returned by the API route. Keep request IDs/statuses only.

Suggested fix
-    console.log("[explain] Initial response:", JSON.stringify(resData))
+    console.log("[explain] Initial response received", {
+      requestId: resData?.result?.requestId ?? resData?.requestId ?? null,
+    })
@@
-      console.log("[explain] Poll result:", JSON.stringify(pollData))
+      console.log("[explain] Poll completed", { requestId })
@@
-      throw new Error("No explanation in poll result: " + JSON.stringify(pollData))
+      throw new Error("Explain flow returned no generated response")
@@
-    throw new Error("Empty response: " + JSON.stringify(resData))
+    throw new Error("Explain flow returned no generated response")
@@
-    console.log("[quiz] Initial response:", JSON.stringify(resData))
+    console.log("[quiz] Initial response received", {
+      requestId: resData?.result?.requestId ?? resData?.requestId ?? null,
+    })
@@
-      console.log("[quiz] Poll result:", JSON.stringify(pollData))
+      console.log("[quiz] Poll completed", { requestId })

Also applies to: 39-49, 69-74

Comment on lines +82 to +85
if (raw) {
const match = typeof raw === "string" ? raw.match(/\{[\s\S]*\}/) : null
const parsed = match ? JSON.parse(match[0]) : raw
return { success: true, data: parsed }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't return malformed quiz data as a success.

If the model returns prose or any non-JSON string, match is null and the raw string is returned with success: true. The quiz API then violates its own contract for callers expecting questions[]. Validate the parsed shape before returning.

Suggested fix
-      if (raw) {
-        const match = typeof raw === "string" ? raw.match(/\{[\s\S]*\}/) : null
-        const parsed = match ? JSON.parse(match[0]) : raw
-        return { success: true, data: parsed }
-      }
+      if (raw) {
+        const parsed =
+          typeof raw === "string"
+            ? (() => {
+                const match = raw.match(/\{[\s\S]*\}/)
+                if (!match) throw new Error("Quiz response did not contain JSON")
+                return JSON.parse(match[0])
+              })()
+            : raw
+
+        if (!Array.isArray(parsed?.questions)) {
+          throw new Error("Quiz response is missing questions[]")
+        }
+
+        return { success: true, data: parsed }
+      }
@@
-    if (raw) {
-      const match = typeof raw === "string" ? raw.match(/\{[\s\S]*\}/) : null
-      const parsed = match ? JSON.parse(match[0]) : raw
-      return { success: true, data: parsed }
-    }
+    if (raw) {
+      const parsed =
+        typeof raw === "string"
+          ? (() => {
+              const match = raw.match(/\{[\s\S]*\}/)
+              if (!match) throw new Error("Quiz response did not contain JSON")
+              return JSON.parse(match[0])
+            })()
+          : raw
+
+      if (!Array.isArray(parsed?.questions)) {
+        throw new Error("Quiz response is missing questions[]")
+      }
+
+      return { success: true, data: parsed }
+    }

Also applies to: 90-94

Comment on lines +43 to +45
"id": "187c2f4b-c23d-4545-abef-73dc897d6b7d",
"role": "user",
"content": "Explain this research paper at {{triggerNode_1.output.level}}level:\n{{triggerNode_1.output.paperContent}}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the missing space in the level prompt.

This renders as Simplelevel / Expertlevel, which weakens the only instruction that selects the explanation depth.

Suggested fix
-              "content": "Explain this research paper at {{triggerNode_1.output.level}}level:\n{{triggerNode_1.output.paperContent}}"
+              "content": "Explain this research paper at the {{triggerNode_1.output.level}} level:\n{{triggerNode_1.output.paperContent}}"

Comment on lines +3 to +6
export const lamaticClient = new Lamatic({
endpoint: process.env.LAMATIC_PROJECT_ENDPOINT ?? "",
projectId: process.env.LAMATIC_PROJECT_ID ?? "",
apiKey: process.env.LAMATIC_PROJECT_API_KEY ?? "",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "lamatic-client.ts" | head -20

Repository: Lamatic/AgentKit

Length of output: 492


🏁 Script executed:

git ls-files | grep "lamatic-client"

Repository: Lamatic/AgentKit

Length of output: 474


🏁 Script executed:

find . -name "*lamatic*" -type f 2>/dev/null | head -20

Repository: Lamatic/AgentKit

Length of output: 1063


🏁 Script executed:

ls -la kits/ 2>/dev/null | head -20

Repository: Lamatic/AgentKit

Length of output: 651


🏁 Script executed:

find . -path "*research-paper-explainer*" -type d 2>/dev/null

Repository: Lamatic/AgentKit

Length of output: 473


🏁 Script executed:

rg "Lamatic" -l 2>/dev/null | head -20

Repository: Lamatic/AgentKit

Length of output: 794


🏁 Script executed:

git ls-files | grep -i lamatic

Repository: Lamatic/AgentKit

Length of output: 1023


🏁 Script executed:

git ls-files | head -50

Repository: Lamatic/AgentKit

Length of output: 2145


🏁 Script executed:

ls -la kits/research-paper-explainer/lib/ 2>/dev/null || echo "Directory not found"

Repository: Lamatic/AgentKit

Length of output: 234


🏁 Script executed:

pwd && git rev-parse --show-toplevel 2>/dev/null && ls -la

Repository: Lamatic/AgentKit

Length of output: 1090


🏁 Script executed:

rg "LAMATIC_PROJECT" -A 2 -B 2 2>/dev/null | head -50

Repository: Lamatic/AgentKit

Length of output: 3016


🏁 Script executed:

rg "process\.env\." -t ts -t tsx | grep -i "??" | head -30

Repository: Lamatic/AgentKit

Length of output: 87


🏁 Script executed:

rg "throw new Error" -A 2 -B 2 | grep -A 2 -B 2 -i "env\|config" | head -40

Repository: Lamatic/AgentKit

Length of output: 2650


🏁 Script executed:

find . -name ".env*" -o -name "*.config*" -o -name "server.ts" 2>/dev/null | head -20

Repository: Lamatic/AgentKit

Length of output: 866


🏁 Script executed:

find . -name "package.json" -type f 2>/dev/null | xargs grep -l "lamatic" 2>/dev/null

Repository: Lamatic/AgentKit

Length of output: 365


🏁 Script executed:

rg "lamaticClient\|from.*lamatic" --type ts --type tsx 2>/dev/null | head -30

Repository: Lamatic/AgentKit

Length of output: 42


🏁 Script executed:

rg "export const.*Client\s*=" --type ts --type tsx 2>/dev/null | head -20

Repository: Lamatic/AgentKit

Length of output: 42


🏁 Script executed:

rg "lamatic\|LAMATIC" --type ts --type tsx --type js -i 2>/dev/null

Repository: Lamatic/AgentKit

Length of output: 42


🌐 Web query:

Lamatic SDK TypeScript constructor requirements endpoint projectId apiKey

💡 Result:

The Lamatic SDK TypeScript constructor requires a configuration object with three mandatory parameters: endpoint (the Lamatic API endpoint), projectId (your project ID), and either apiKey or accessToken (but not both) for authentication. Example: import { Lamatic } from "lamatic"; const lamatic = new Lamatic({ endpoint: "your-endpoint", // Required projectId: "your-project-id", // Required apiKey: "your-api-key", // Required if not using accessToken // OR accessToken: "your-access-token" }); The constructor throws an error if any required field is missing or if neither apiKey nor accessToken is provided.

Citations:


🏁 Script executed:

rg "if\s*\(!.*process\.env|if\s*\(!.*process\.env.*\)" --type ts --type tsx -A 2 | head -40

Repository: Lamatic/AgentKit

Length of output: 87


🏁 Script executed:

rg "Missing.*configuration|required.*environment|throw.*env" --type ts --type tsx --type js 2>/dev/null | head -20

Repository: Lamatic/AgentKit

Length of output: 42


🏁 Script executed:

find . -path "*agentic*lamatic*" 2>/dev/null

Repository: Lamatic/AgentKit

Length of output: 257


🏁 Script executed:

rg "new Lamatic" --type ts --type tsx 2>/dev/null

Repository: Lamatic/AgentKit

Length of output: 42


🏁 Script executed:

rg "getRequiredEnv|requireEnv|validateEnv" --type ts --type tsx --type js 2>/dev/null

Repository: Lamatic/AgentKit

Length of output: 42


🏁 Script executed:

rg "process\.env\." --type ts --type tsx | grep -v "??" | head -30

Repository: Lamatic/AgentKit

Length of output: 87


🏁 Script executed:

git log --oneline -20 2>/dev/null | head -20

Repository: Lamatic/AgentKit

Length of output: 119


🏁 Script executed:

git ls-files | wc -l && git ls-files | head -100

Repository: Lamatic/AgentKit

Length of output: 4810


🏁 Script executed:

rg "endpoint.*projectId\|projectId.*endpoint" --type ts --type tsx --type js 2>/dev/null

Repository: Lamatic/AgentKit

Length of output: 42


🏁 Script executed:

find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" \) 2>/dev/null | wc -l

Repository: Lamatic/AgentKit

Length of output: 63


🏁 Script executed:

ls -la 2>/dev/null && echo "---" && ls -la kits 2>/dev/null || true

Repository: Lamatic/AgentKit

Length of output: 1648


🏁 Script executed:

find . -name "*.ts" -type f 2>/dev/null | xargs grep -l "new.*Client\|export const.*client" 2>/dev/null | head -20

Repository: Lamatic/AgentKit

Length of output: 42


🌐 Web query:

"fail fast" pattern environment variable validation TypeScript Node.js

💡 Result:

In Node.js/TypeScript, the “fail fast” env-var validation pattern means: load env vars once at process start, validate/coerce them against a schema, and crash immediately (exit code 1 / throw) if anything is missing or invalid—so you never reach “runtime” with a broken configuration.

Typical structure

  • src/env.ts (or src/config.ts) is imported first from your entrypoint (src/index.ts / src/server.ts)
  • It:
    1. loads .env (optional, e.g. via dotenv)
    2. validates + coerces
    3. exports a typed config object (so the rest of the app never reads process.env directly)

Option A (common): envalid (built for fail-fast)

cleanEnv() exits (Node) or throws if required vars are missing/invalid by default. [1]

// src/env.ts
import 'dotenv/config';
import { cleanEnv, str, num, bool, url } from 'envalid';

export const env = cleanEnv(process.env, {
  NODE_ENV: str({ choices: ['development', 'test', 'production'] }),
  PORT: num({ default: 3000 }),
  DATABASE_URL: url(),
  LOG_JSON: bool({ default: false }),
});
// src/index.ts (import first!)
import { env } from './env';
import { startServer } from './server';

startServer({ port: env.PORT });

Option B: convict (schema + early validation)

Convict promotes a config schema and validation/early failures when config is wrong. [2]

Option C: presence-only guard with .env.example

If you mainly want “did we define all required keys?”, dotenv-safe loads .env and throws if any variables listed in .env.example are missing. [3]

Sources: envalid (fail-fast default behavior) [1], convict (schema + early validation) [2], dotenv-safe (throws on missing keys vs .env.example) [3].


🏁 Script executed:

cat -n kits/research-paper-explainer/lib/lamatic-client.ts

Repository: Lamatic/AgentKit

Length of output: 351


🏁 Script executed:

cat -n kits/agentic/generation/lib/lamatic-client.ts

Repository: Lamatic/AgentKit

Length of output: 842


🏁 Script executed:

cat -n kits/embed/sheets/lib/lamatic-client.ts

Repository: Lamatic/AgentKit

Length of output: 833


🏁 Script executed:

cat -n kits/sample/content-generation/lib/lamatic-client.ts

Repository: Lamatic/AgentKit

Length of output: 842


Fail fast on missing Lamatic configuration.

This file lacks the validation pattern used in other kits. Defaulting endpoint, projectId, and apiKey to "" still creates a client, so a bad deploy fails only during the first flow call instead of at startup. Validate required env vars before constructing the SDK client.

Suggested fix
+const endpoint = process.env.LAMATIC_PROJECT_ENDPOINT;
+const projectId = process.env.LAMATIC_PROJECT_ID;
+const apiKey = process.env.LAMATIC_PROJECT_API_KEY;
+
+if (!endpoint || !projectId || !apiKey) {
+  throw new Error(
+    "Missing Lamatic configuration: LAMATIC_PROJECT_ENDPOINT, LAMATIC_PROJECT_ID, and LAMATIC_PROJECT_API_KEY are required."
+  );
+}
+
 export const lamaticClient = new Lamatic({
-  endpoint: process.env.LAMATIC_PROJECT_ENDPOINT ?? "",
-  projectId: process.env.LAMATIC_PROJECT_ID ?? "",
-  apiKey: process.env.LAMATIC_PROJECT_API_KEY ?? "",
+  endpoint,
+  projectId,
+  apiKey,
 });

Comment on lines +8 to +13
async headers() {
return [
{
source: '/api/:path*',
headers: [{ key: 'Connection', value: 'keep-alive' }],
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

For Next.js 14.2.5 on Vercel, is adding Connection: keep-alivevianext.config.mjs -> headers()for/api/:path* recommended or invalid under HTTP/2+?

💡 Result:

Adding Connection: keep-alive via next.config.mjs -> headers for /api/:path* is invalid under HTTP/2+, which Vercel uses to serve Next.js deployments. HTTP/2 prohibits connection-specific header fields like Connection (RFC 7540, section 8.1.2.2 and 9.1.2). Servers/clients must not generate them and should treat messages containing them as malformed (though many ignore them). Vercel automatically serves over HTTP/2 (h2 via ALPN/TLS), making the header meaningless and potentially problematic. Keep-alive is persistent by default in HTTP/2 without needing this header. Next.js docs show headers for custom/security headers (e.g., CORS, HSTS) but not Connection. On Vercel, API routes run in serverless/edge functions where connection management is handled by the platform.

Citations:


Remove Connection: keep-alive header—it violates HTTP/2 and is unnecessary.

HTTP/2 (RFC 7540 section 8.1.2.2) explicitly prohibits connection-specific headers like Connection. Vercel serves Next.js deployments over HTTP/2 by default. Keep-alive is the default connection mode in HTTP/2 and does not need to be set explicitly. This header will either be ignored or cause protocol violations.

Proposed fix
 const nextConfig = {
   experimental: {
     serverActions: {
       bodySizeLimit: '2mb',
     },
   },
-  async headers() {
-    return [
-      {
-        source: '/api/:path*',
-        headers: [{ key: 'Connection', value: 'keep-alive' }],
-      },
-    ];
-  },
 };
 export default nextConfig;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants