Conversation
Replace generateText with generateObject for type-safe AI responses. The copilot completion endpoint now uses a Zod schema to define the response structure, ensuring consistent and validated outputs.
Reviewer's GuideRefactors the copilot API routes to use Sequence diagram for copilot POST route using generateObject with Zod schemasequenceDiagram
actor User
participant Client as FrontendClient
participant Route as CopilotApiRoute
participant AISDK as AISDK_generateObject
participant Zod as Zod_completionSchema
User->>Client: Trigger editor copilot completion
Client->>Route: POST /api/ai/copilot { model, prompt, system }
activate Route
Route->>Route: Parse JSON body
Route->>AISDK: generateObject(model, prompt, schema, schemaDescription, schemaName, abortSignal, system, temperature)
activate AISDK
AISDK->>Zod: Validate response with completionSchema
Zod-->>AISDK: Validated object { text }
AISDK-->>Route: result.object
deactivate AISDK
Route-->>Client: 200 OK { text }
deactivate Route
Client-->>User: Display completion suggestion in editor
alt Request aborted
Route->>AISDK: generateObject(..., abortSignal)
AISDK-->>Route: Throw AbortError
Route-->>Client: 408 Request Timeout
end
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Summary of ChangesHello @arthrod, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly enhances the Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
PR Compliance Guide 🔍(Compliance updated until commit a33d3ae)Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label Previous compliance checksCompliance check up to commit 9ec2fa9
Compliance check up to commit 34f4404
Compliance check up to commit c58bb79
Compliance check up to commit 2dd3490
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Warning Rate limit exceeded@arthrod has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 4 minutes and 59 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (1)
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. 📝 WalkthroughSummary by CodeRabbit
✏️ Tip: You can customize this high-level summary in your review settings. WalkthroughThe PR updates AI Copilot API routes in two locations ( Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Comment |
There was a problem hiding this comment.
Hey there - I've reviewed your changes - here's some feedback:
- The
POSThandlers now returnresult.objectinstead of the fullgenerateObjectresult; consider typing the handler’s response explicitly based onz.infer<typeof completionSchema>to keep the API surface clearly aligned with the schema. - The
completionSchemadefinition is duplicated between the template and registry routes; consider extracting a shared schema to avoid divergence if the completion shape changes in the future.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The `POST` handlers now return `result.object` instead of the full `generateObject` result; consider typing the handler’s response explicitly based on `z.infer<typeof completionSchema>` to keep the API surface clearly aligned with the schema.
- The `completionSchema` definition is duplicated between the template and registry routes; consider extracting a shared schema to avoid divergence if the completion shape changes in the future.
## Individual Comments
### Comment 1
<location> `templates/plate-playground-template/src/app/api/ai/copilot/route.ts:26` </location>
<code_context>
- const result = await generateText({
+ const result = await generateObject({
abortSignal: req.signal,
- maxOutputTokens: 50,
model: `openai/${model}`,
prompt,
</code_context>
<issue_to_address>
**suggestion (performance):** Dropping `maxOutputTokens` may lead to longer generations and higher latency/cost.
The original cap also prevented runaway generations. If `generateObject` supports it, consider specifying a similar token limit so behavior stays consistent with the prior endpoint, especially given how often this is called from the editor.
Suggested implementation:
```typescript
const result = await generateObject({
abortSignal: req.signal,
maxOutputTokens: 50,
model: `openai/${model}`,
prompt,
schema: completionSchema,
schemaDescription: 'A text completion suggestion for the editor',
schemaName: 'completion',
system,
temperature: 0.7,
});
```
If `generateObject` in your SDK uses a different option name for limiting output (e.g. `maxTokens` instead of `maxOutputTokens`), adjust the property name accordingly while keeping the value at 50 to match prior behavior.
</issue_to_address>
### Comment 2
<location> `apps/www/src/registry/app/api/ai/copilot/route.ts:40` </location>
<code_context>
});
- return NextResponse.json(result);
+ return NextResponse.json(result.object);
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
</code_context>
<issue_to_address>
**suggestion (bug_risk):** The registry example now returns a different JSON shape than before.
This change alters the response contract for the registry example: consumers previously received the full `generateText` result (including metadata), but now only `result.object`. If this isn’t intended, either restore a richer shape closer to the original or clearly document that the endpoint now returns only `{ text: string }`.
```suggestion
// Return the full generateText result to preserve the original response contract
return NextResponse.json(result);
```
</issue_to_address>
### Comment 3
<location> `apps/www/src/registry/app/api/ai/copilot/route.ts:29-36` </location>
<code_context>
- const result = await generateText({
+ const result = await generateObject({
abortSignal: req.signal,
- maxOutputTokens: 50,
model: `openai/${model}`,
prompt,
</code_context>
<issue_to_address>
**suggestion:** Removing the output token limit from the registry example may make it less safe as sample code.
Since this is example/registry code, it’s helpful to demonstrate an explicit output limit as a best practice. If `generateObject` supports a token limit, consider adding it here (even if the production route doesn’t) to promote more controlled usage.
```suggestion
const result = await generateObject({
abortSignal: req.signal,
maxOutputTokens: 50,
model: `openai/${model}`,
prompt,
schema: completionSchema,
schemaDescription: 'A text completion suggestion for the editor',
schemaName: 'completion',
system,
temperature: 0.7,
});
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
PR Code Suggestions ✨Latest suggestions up to a33d3ae
Previous suggestionsSuggestions up to commit 9ec2fa9
✅ Suggestions up to commit 9ec2fa9
Suggestions up to commit 34f4404
✅ Suggestions up to commit c58bb79
✅ Suggestions up to commit c58bb79
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Note Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it. Generating unit tests... This may take up to 20 minutes. |
There was a problem hiding this comment.
Code Review
This pull request successfully migrates the copilot API endpoint from generateText to generateObject, enabling type-safe structured outputs using a Zod schema. The changes correctly introduce the zod import, define a completionSchema, and update the API call parameters to leverage the new structured output capabilities. The maxOutputTokens parameter has been appropriately removed, and the response now correctly returns result.object for the structured data. The implementation is clean and directly addresses the stated objective of the pull request.
|
Persistent suggestions updated to latest commit c58bb79 |
There was a problem hiding this comment.
Actionable comments posted: 2
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
apps/www/src/registry/app/api/ai/copilot/route.ts(2 hunks)templates/plate-playground-template/src/app/api/ai/copilot/route.ts(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Sourcery review
🔇 Additional comments (4)
apps/www/src/registry/app/api/ai/copilot/route.ts (3)
3-5: LGTM! Clean imports for structured outputs.The imports correctly bring in
generateObjectfrom the AI SDK and Zod for schema validation.
7-9: LGTM! Well-defined schema for completion responses.The Zod schema is appropriately scoped with a descriptive text field, which will help the AI model understand the expected output format.
29-38: No changes needed — thegenerateObjectcall correctly uses supported parameters.The
ailibrary v5.0.28 fully supports theschema,schemaDescription, andschemaNameparameters. The code is correctly implemented.templates/plate-playground-template/src/app/api/ai/copilot/route.ts (1)
3-9: LGTM! Imports and schema match the registry example.The imports and schema definition are consistent with the registry implementation.
| // Return the full generateText result to preserve the original response contract | ||
| return NextResponse.json(result); |
There was a problem hiding this comment.
Fix outdated comment reference.
The comment references "generateText result" but the code now uses generateObject.
Apply this diff:
- // Return the full generateText result to preserve the original response contract
+ // Return the full generateObject result to preserve the original response contract
return NextResponse.json(result);📝 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.
| // Return the full generateText result to preserve the original response contract | |
| return NextResponse.json(result); | |
| // Return the full generateObject result to preserve the original response contract | |
| return NextResponse.json(result); |
🤖 Prompt for AI Agents
In apps/www/src/registry/app/api/ai/copilot/route.ts around lines 40 to 41, the
inline comment incorrectly references "generateText result" while the code
returns the result from generateObject; update the comment to accurately state
that it returns the full generateObject result (e.g., "Return the full
generateObject result to preserve the original response contract") so the
comment matches the current code.
templates/plate-playground-template/src/app/api/ai/copilot/route.ts
Outdated
Show resolved
Hide resolved
|
Note Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it. Generating unit tests... This may take up to 20 minutes. |
|
Note Unit test generation is an Early Access feature. Expect some limitations and changes as we gather feedback and continue to improve it. Generating unit tests... This may take up to 20 minutes. |
…r AI Copilot API routes
|
🎯 Unit Test Generation Post-Process Complete The unit test generation and CI/CD fixing process has completed after reaching the maximum number of iterations. Please review the generated tests and any remaining issues manually. |
|
Creating a PR to put the unit tests in... The changes have been created in this pull request: View PR |
|
🎯 Unit Test Generation Post-Process Complete The unit test generation and CI/CD fixing process has completed after reaching the maximum number of iterations. Please review the generated tests and any remaining issues manually. |
|
Creating a PR to put the unit tests in... The changes have been created in this pull request: View PR |
CodeRabbit Generated Unit Tests: Add comprehensive Bun test suites for AI Copilot API routes
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (2)
apps/www/src/registry/app/api/ai/copilot/route.ts (1)
40-41: Fix outdated comment reference.The comment still references "generateText" but the code now uses
generateObject.templates/plate-playground-template/src/app/api/ai/copilot/route.ts (1)
40-40: Inconsistent response contract with registry example.This file returns
result.objectwhile the registry version returns the fullresult. This inconsistency was previously flagged.
🧹 Nitpick comments (3)
apps/www/src/registry/app/api/ai/copilot/route.spec.ts (1)
462-473: Improve malformed JSON test assertion.The test catches errors but doesn't assert anything meaningful about the error or response behavior. Consider using
expect().rejects.toThrow()pattern for consistency with similar tests, or verify the route returns a proper error response.- it('should handle malformed JSON in request body', async () => { - const request = new NextRequest('http://localhost:3000/api/ai/copilot', { - method: 'POST', - body: 'not valid json', - }); - - try { - await POST(request); - } catch (error) { - expect(error).toBeDefined(); - } - }); + it('should handle malformed JSON in request body', async () => { + const request = new NextRequest('http://localhost:3000/api/ai/copilot', { + method: 'POST', + body: 'not valid json', + }); + + await expect(POST(request)).rejects.toThrow(); + });templates/plate-playground-template/src/app/api/ai/copilot/route.test.ts (1)
847-873: Test mocks schema-incompatible response.The mock returns
{ text, metadata }but thecompletionSchemaonly defines{ text: z.string() }. While this tests the passthrough behavior, it's inconsistent with the actual schema validation thatgenerateObjectwould perform.Consider either:
- Updating the test to only return schema-compliant data, or
- Adding a comment explaining this tests raw passthrough behavior (assuming
generateObjecthas already validated)templates/plate-playground-template/src/app/api/ai/copilot/route.spec.ts (1)
17-33: Make env restore robust to empty-string values
originalEnvis checked via truthiness, so an original value of''would be treated as “unset” and removed inafterEach, changing process state. Checking explicitly forundefinedis safer while still restoring previous config.- const originalEnv = process.env.AI_GATEWAY_API_KEY; + const originalEnv = process.env.AI_GATEWAY_API_KEY; afterEach(() => { mockGenerateObject.mockClear(); // Restore original env - if (originalEnv) { - process.env.AI_GATEWAY_API_KEY = originalEnv; - } else { - delete process.env.AI_GATEWAY_API_KEY; - } + if (originalEnv !== undefined) { + process.env.AI_GATEWAY_API_KEY = originalEnv; + } else { + delete process.env.AI_GATEWAY_API_KEY; + } });
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
apps/www/src/registry/app/api/ai/copilot/route.spec.ts(1 hunks)apps/www/src/registry/app/api/ai/copilot/route.test.ts(1 hunks)apps/www/src/registry/app/api/ai/copilot/route.ts(2 hunks)templates/plate-playground-template/src/app/api/ai/copilot/route.spec.ts(1 hunks)templates/plate-playground-template/src/app/api/ai/copilot/route.test.ts(1 hunks)templates/plate-playground-template/src/app/api/ai/copilot/route.ts(2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Sourcery review
🔇 Additional comments (10)
apps/www/src/registry/app/api/ai/copilot/route.ts (2)
3-9: LGTM! Clean migration to structured outputs.The Zod schema definition is concise and correctly typed. The
.describe()call provides useful context for the AI model.
29-38: LGTM! Correct usage of generateObject with schema validation.The
generateObjectcall properly passes the schema along with metadata (schemaName,schemaDescription). Temperature is hardcoded appropriately for consistent copilot behavior.templates/plate-playground-template/src/app/api/ai/copilot/route.ts (1)
3-9: LGTM! Schema definition matches the registry version.The Zod schema is correctly defined with the same structure as the registry route.
apps/www/src/registry/app/api/ai/copilot/route.spec.ts (2)
1-14: LGTM! Well-structured mock setup.The mock correctly simulates the
generateObjectresponse structure withobject,usage, andfinishReasonfields.
587-640: LGTM! Response format tests correctly verify full result is returned.These tests properly verify that the registry route returns the complete
generateObjectresult includingobject,usage,finishReason, and other metadata fields.apps/www/src/registry/app/api/ai/copilot/route.test.ts (1)
569-591: LGTM! Response format validation is correct.The test correctly verifies that all fields from the
generateObjectresult are preserved in the response.templates/plate-playground-template/src/app/api/ai/copilot/route.test.ts (3)
1-14: LGTM! Mock setup matches the other test files.Consistent mock structure across test files aids maintainability.
150-153: Good explicit test for the different response contract.This test clearly documents that the template version returns only
result.object(the text field), which differs from the registry version that returns the full result.
820-845: Excellent documentation of intentional API differences.This test suite explicitly documents the response contract difference between the template and registry versions, making it clear this is intentional behavior rather than a bug.
templates/plate-playground-template/src/app/api/ai/copilot/route.spec.ts (1)
735-757: Overall: strong coverage and clear backward‑compatibility checkThe suite thoroughly exercises authentication, parameter plumbing, schema usage, error handling, and response shaping, and this final test nicely encodes the intended response contract for existing consumers. Aside from the minor issues called out above, the structure and intent look solid.
| import { describe, it, expect, beforeEach, afterEach, mock } from 'bun:test'; | ||
| import { NextRequest } from 'next/server'; | ||
| import { POST } from './route'; | ||
|
|
||
| // Mock the AI SDK | ||
| const mockGenerateObject = mock(() => Promise.resolve({ | ||
| object: { text: 'completion text' }, | ||
| usage: { promptTokens: 10, completionTokens: 20 }, | ||
| finishReason: 'stop', | ||
| })); | ||
|
|
||
| mock.module('ai', () => ({ | ||
| generateObject: mockGenerateObject, | ||
| })); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if both test files exist and compare their similarity
echo "=== Looking for test files in the directory ==="
fd -e ts -p "route\.(spec|test)\.ts" apps/www/src/registry/app/api/ai/copilot/ 2>/dev/null || echo "No matching files found"
echo ""
echo "=== Checking specific file paths ==="
test -f "apps/www/src/registry/app/api/ai/copilot/route.spec.ts" && echo "route.spec.ts EXISTS" || echo "route.spec.ts NOT FOUND"
test -f "apps/www/src/registry/app/api/ai/copilot/route.test.ts" && echo "route.test.ts EXISTS" || echo "route.test.ts NOT FOUND"
echo ""
echo "=== Directory listing ==="
ls -la apps/www/src/registry/app/api/ai/copilot/ 2>/dev/null | grep -E "\.(spec|test)\.ts$" || echo "No spec/test files found in directory"Repository: arthrod/plate
Length of output: 449
🏁 Script executed:
#!/bin/bash
# Compare the structure and content of both test files
echo "=== Comparing test file structure ==="
echo "File: route.spec.ts"
grep -E "^\s*(describe|it|test)\(" apps/www/src/registry/app/api/ai/copilot/route.spec.ts | head -20
echo ""
echo "File: route.test.ts"
grep -E "^\s*(describe|it|test)\(" apps/www/src/registry/app/api/ai/copilot/route.test.ts | head -20
echo ""
echo "=== Test counts ==="
echo "route.spec.ts test/describe count: $(grep -cE "^\s*(describe|it|test)\(" apps/www/src/registry/app/api/ai/copilot/route.spec.ts)"
echo "route.test.ts test/describe count: $(grep -cE "^\s*(describe|it|test)\(" apps/www/src/registry/app/api/ai/copilot/route.test.ts)"
echo ""
echo "=== First 50 lines of route.spec.ts ==="
head -50 apps/www/src/registry/app/api/ai/copilot/route.spec.ts
echo ""
echo "=== First 50 lines of route.test.ts ==="
head -50 apps/www/src/registry/app/api/ai/copilot/route.test.tsRepository: arthrod/plate
Length of output: 5623
🏁 Script executed:
#!/bin/bash
# Get a more detailed comparison of the test cases
echo "=== Unique test descriptions in route.spec.ts ==="
grep -oE "it\('([^']+)" apps/www/src/registry/app/api/ai/copilot/route.spec.ts | sed "s/it('//g" | sort > /tmp/spec_tests.txt
cat /tmp/spec_tests.txt
echo ""
echo "=== Unique test descriptions in route.test.ts ==="
grep -oE "it\('([^']+)" apps/www/src/registry/app/api/ai/copilot/route.test.ts | sed "s/it('//g" | sort > /tmp/test_tests.txt
cat /tmp/test_tests.txt
echo ""
echo "=== Tests in route.spec.ts but NOT in route.test.ts ==="
comm -23 /tmp/spec_tests.txt /tmp/test_tests.txt
echo ""
echo "=== Tests in route.test.ts but NOT in route.spec.ts ==="
comm -13 /tmp/spec_tests.txt /tmp/test_tests.txtRepository: arthrod/plate
Length of output: 5685
Consolidate duplicate test files to reduce maintenance burden.
This repository has two test suites for the same route: route.spec.ts and route.test.ts. While not identical, they contain significant overlap—both test the same core scenarios (authentication, happy path, edge cases, error handling) with similar test coverage but different naming conventions and slight variations. The test descriptions show near-equivalent coverage with some tests unique to each file (e.g., concurrent request handling in .test.ts, model name prepending in .spec.ts), but merging them into a single file would eliminate the need to maintain parallel test suites.
Consider consolidating into a single test file following your project's conventions (either .spec.ts or .test.ts) to avoid:
- Duplicated test maintenance when fixing or updating behavior
- Confusion about which file contains the authoritative test suite
- Running overlapping tests during CI
🤖 Prompt for AI Agents
In apps/www/src/registry/app/api/ai/copilot/route.test.ts lines 1-14, there is a
duplicate test suite for the same route also covered by route.spec.ts;
consolidate into a single canonical test file (pick .spec.ts or .test.ts per
project convention), merge unique tests from both files into that one file,
unify and dedupe shared setup (imports, mocks, hooks like beforeEach/afterEach),
remove the redundant file, and update any test-runner/config references so only
the chosen filename remains in CI.
| it('should prefer apiKey from body over env variable', async () => { | ||
| process.env.AI_GATEWAY_API_KEY = 'env-key'; | ||
|
|
||
| const request = new NextRequest('http://localhost:3000/api/ai/copilot', { | ||
| method: 'POST', | ||
| body: JSON.stringify({ | ||
| apiKey: 'body-key', | ||
| model: 'gpt-4', | ||
| prompt: 'Test', | ||
| system: 'Test', | ||
| }), | ||
| }); | ||
|
|
||
| const response = await POST(request); | ||
|
|
||
| expect(response.status).toBe(200); | ||
| }); |
There was a problem hiding this comment.
Strengthen the “prefer apiKey from body over env” assertion
This test only checks for a 200 status, which would also be true if the implementation always preferred the env key. It doesn’t actually verify that the body apiKey wins over the env value.
You can assert on the arguments passed to generateObject (or however the key is forwarded) so the test fails if precedence is reversed.
const response = await POST(request);
expect(response.status).toBe(200);
+ const callArgs = mockGenerateObject.mock.calls[0][0];
+ // Adjust the property path to match how the api key is actually forwarded.
+ // For example, if you pass it directly:
+ // expect(callArgs.apiKey).toBe('body-key');
+ //
+ // Or, if you wrap it in a client:
+ // expect(callArgs.client.apiKey).toBe('body-key');Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In templates/plate-playground-template/src/app/api/ai/copilot/route.spec.ts
around lines 94-110, the test only asserts a 200 response but doesn't verify
that the body apiKey is used instead of the env var; update the test to spy/mock
the function that receives the API key (e.g., generateObject or whatever
internal call forwards the key), set process.env.AI_GATEWAY_API_KEY = 'env-key',
send the POST with body.apiKey = 'body-key', and assert the mocked function was
invoked with the apiKey 'body-key' (and not 'env-key'); ensure you restore/clear
the mock and env after the test to avoid leaking state.
| it('should handle malformed JSON in request body', async () => { | ||
| const request = new NextRequest('http://localhost:3000/api/ai/copilot', { | ||
| method: 'POST', | ||
| body: 'not valid json', | ||
| }); | ||
|
|
||
| try { | ||
| await POST(request); | ||
| } catch (error) { | ||
| expect(error).toBeDefined(); | ||
| } | ||
| }); |
There was a problem hiding this comment.
Malformed JSON test currently passes even if POST succeeds
In this test, if POST(request) does not throw, the catch block is never executed and the test still passes (there are no assertions after the await POST(request) call). So it doesn’t actually enforce any behavior for malformed JSON.
Consider asserting explicitly that the call either rejects or returns a specific error status.
For example, if the route is expected to throw on invalid JSON:
- try {
- await POST(request);
- } catch (error) {
- expect(error).toBeDefined();
- }
+ await expect(POST(request)).rejects.toBeDefined();Or, if you prefer a graceful HTTP response (recommended), assert on the status instead:
const response = await POST(request);
expect(response.status).toBe(400); // or whatever status you choose for bad JSON🤖 Prompt for AI Agents
In templates/plate-playground-template/src/app/api/ai/copilot/route.spec.ts
around lines 530-541, the malformed JSON test currently just awaits
POST(request) and only asserts inside a catch block, so it will pass if POST
does not throw; update the test to explicitly assert the expected behavior:
either assert that the call rejects (e.g. use await
expect(POST(request)).rejects.toThrow() or similar) if the route should throw on
invalid JSON, or call await POST(request) and assert the returned
response.status equals the chosen error status (e.g. 400) if the route should
handle it gracefully. Ensure the test contains a single explicit assertion so it
fails when behavior differs.
| it('should handle result.object being undefined gracefully', async () => { | ||
| mockGenerateObject.mockImplementationOnce(async () => ({ | ||
| object: undefined, | ||
| })); | ||
|
|
||
| const request = new NextRequest('http://localhost:3000/api/ai/copilot', { | ||
| method: 'POST', | ||
| body: JSON.stringify({ | ||
| apiKey: 'test-key', | ||
| model: 'gpt-4', | ||
| prompt: 'Test', | ||
| system: 'Test', | ||
| }), | ||
| }); | ||
|
|
||
| const response = await POST(request); | ||
| const data = await response.json(); | ||
|
|
||
| expect(data).toBeUndefined(); | ||
| }); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find the route.ts file in the same directory as the test
find templates/plate-playground-template/src/app/api/ai/copilot -name "route.ts" -o -name "route.js"Repository: arthrod/plate
Length of output: 124
🏁 Script executed:
# Also check what files are in that directory
ls -la templates/plate-playground-template/src/app/api/ai/copilot/Repository: arthrod/plate
Length of output: 357
🏁 Script executed:
cat -n templates/plate-playground-template/src/app/api/ai/copilot/route.tsRepository: arthrod/plate
Length of output: 1625
🌐 Web query:
Next.js NextResponse.json undefined behavior
💡 Result:
Short answer
- NextResponse.json(undefined) is unreliable: NextResponse uses JSON serialization and passing undefined produces no valid JSON body (JSON.stringify(undefined) -> undefined), which can lead to an empty response body or framework errors/validation depending on runtime and status code. Use null or an explicit value instead. [1][2][3]
Why
- NextResponse.json is a thin helper that serializes the value to JSON before returning a Response (see NextResponse docs). JSON.stringify(undefined) returns undefined (no JSON text), so sending undefined as the body can produce an empty body or trigger Next.js validation that complains about invalid/unsendable response bodies. [1][2]
Observed problems / real issues reported
- Dev/prod inconsistencies and runtime errors have been reported when using NextResponse.json in edge/dev environments. There are GitHub issues describing Dev-mode errors and other unexpected behavior from malformed responses. [3][4]
Recommended fixes
- Return a concrete JSON value: NextResponse.json(null) or NextResponse.json({}) or NextResponse.json({ message: '...' }) instead of undefined.
- If you intend to return "no content", use an appropriate status with no body (e.g., return new Response(null, { status: 204 })) rather than NextResponse.json(undefined).
- When returning from middleware/edge handlers make sure you return a valid NextResponse/Response object (see docs). [1][2]
References
- NextResponse docs — Next.js. [NextResponse functions/docs]. [1]
- Next.js docs: invalid API route status/body (204/304 + body) and response validation. [2]
- GitHub issues showing NextResponse.json/dev/runtime problems. [3][4]
(If you want, I can fetch a small repro or show the exact code to replace NextResponse.json(undefined) with a safe pattern.)
🏁 Script executed:
# Let me also check the test file to see what it actually asserts
sed -n '712,731p' templates/plate-playground-template/src/app/api/ai/copilot/route.spec.tsRepository: arthrod/plate
Length of output: 634
The route handler should explicitly handle undefined from result.object instead of relying on NextResponse.json(undefined)
Line 40 of route.ts returns NextResponse.json(result.object) without checking if result.object is undefined. When result.object is undefined, JSON.stringify(undefined) produces an invalid JSON body, which can cause runtime inconsistencies and unreliable behavior depending on the environment.
Choose an explicit contract—either return null, {}, or a 204 status with no body—and align both the route and test accordingly. For example:
- return NextResponse.json(result.object);
+ return NextResponse.json(result.object ?? null);Then update the test assertion to match:
- expect(data).toBeUndefined();
+ expect(data).toBeNull();Committable suggestion skipped: line range outside the PR's diff.
|
@coderabbitai review critic. Ur own review. Provide all the changes, if any, require. Dto. Make this work |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 2607 @arthrod: I found a critical inconsistency in the implementation that needs to be fixed: 🚨 Critical IssueThe two route files have different return behaviors:
According to your PR description, you intended to "return the validated object (result.object) rather than the full raw result", but the apps/www version is still returning the full result. Required FixFile: Change line 36-37 from: // Return the full generateText result to preserve the original response contract
return NextResponse.json(result);To: return NextResponse.json(result.object);Complete Corrected ImplementationHere's the corrected version of import type { NextRequest } from 'next/server';
import { generateObject } from 'ai';
import { NextResponse } from 'next/server';
import { z } from 'zod';
const completionSchema = z.object({
text: z.string().describe('The completion text suggestion'),
});
export async function POST(req: NextRequest) {
const {
apiKey: key,
model = 'gpt-4o-mini',
prompt,
system,
} = await req.json();
const apiKey = key || process.env.AI_GATEWAY_API_KEY;
if (!apiKey) {
return NextResponse.json(
{ error: 'Missing ai gateway API key.' },
{ status: 401 }
);
}
try {
const result = await generateObject({
abortSignal: req.signal,
model: `openai/${model}`,
prompt,
schema: completionSchema,
schemaDescription: 'A text completion suggestion for the editor',
schemaName: 'completion',
system,
temperature: 0.7,
});
return NextResponse.json(result.object);
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
return NextResponse.json(null, { status: 408 });
}
return NextResponse.json(
{ error: 'Failed to process AI request' },
{ status: 500 }
);
}
}Summary
Make this change and your implementation will be correct and consistent across both files. |
…te.ts Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
Looks like there are a few issues preventing this PR from being merged!
If you'd like me to help, just leave a comment, like Feel free to include any additional details that might help me get this PR into a better state. You can manage your notification settings |
|
Persistent suggestions updated to latest commit 9ec2fa9 |
User description
Replace generateText with generateObject for type-safe AI responses. The copilot completion endpoint now uses a Zod schema to define the response structure, ensuring consistent and validated outputs.
Checklist
yarn typecheckyarn lint:fixyarn testyarn brlyarn changesetPR Type
Enhancement, Tests
Description
Replace
generateTextwithgenerateObjectfor type-safe AI responsesAdd Zod schema validation for structured completion outputs
Remove
maxOutputTokensparameter from API callsAdd comprehensive unit test suites for copilot API routes
Diagram Walkthrough
File Walkthrough
route.ts
Convert to generateObject with Zod schema validationapps/www/src/registry/app/api/ai/copilot/route.ts
generateTextwithgenerateObjectfor structured outputscompletionSchemaZod object defining text field validationschema,schemaName,schemaDescriptionmaxOutputTokensparametergenerateObjectresult preserving original responsecontract
route.ts
Convert to generateObject with Zod schema validationtemplates/plate-playground-template/src/app/api/ai/copilot/route.ts
generateTextwithgenerateObjectfor structured outputscompletionSchemaZod object defining text field validationschema,schemaName,schemaDescriptionmaxOutputTokensparametergenerateObjectresult to preserve response contractroute.spec.ts
Add comprehensive unit test suite for copilot APIapps/www/src/registry/app/api/ai/copilot/route.spec.ts
scenarios
prompts
route.test.ts
Add comprehensive unit test suite for copilot APIapps/www/src/registry/app/api/ai/copilot/route.test.ts
route.spec.ts
Add comprehensive unit test suite for template copilot APItemplates/plate-playground-template/src/app/api/ai/copilot/route.spec.ts
route.test.ts
Add comprehensive unit test suite for template copilot APItemplates/plate-playground-template/src/app/api/ai/copilot/route.test.ts
result.object)