Skip to content

Commit 98f7278

Browse files
committed
deprecate copilot api v1 route
1 parent 961aa12 commit 98f7278

4 files changed

Lines changed: 41 additions & 145 deletions

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Tests for the deprecated v1 copilot chat API route
3+
*
4+
* @vitest-environment node
5+
*/
6+
import { NextRequest } from 'next/server'
7+
import { describe, expect, it } from 'vitest'
8+
import { POST } from '@/app/api/v1/copilot/chat/route'
9+
10+
const URL = 'http://localhost:3000/api/v1/copilot/chat'
11+
12+
describe('Deprecated v1 copilot chat route', () => {
13+
it('POST returns 410 with a success:false error body', async () => {
14+
const request = new NextRequest(URL, {
15+
method: 'POST',
16+
headers: { 'Content-Type': 'application/json', 'x-api-key': 'sk-test' },
17+
body: JSON.stringify({ message: 'hello' }),
18+
})
19+
const response = await POST(request)
20+
expect(response.status).toBe(410)
21+
22+
const body = (await response.json()) as { success?: boolean; error?: string }
23+
expect(body.success).toBe(false)
24+
expect(body.error).toContain('deprecated')
25+
})
26+
})
Lines changed: 12 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,151 +1,18 @@
1-
import { createLogger } from '@sim/logger'
2-
import { toError } from '@sim/utils/errors'
3-
import { generateId } from '@sim/utils/id'
4-
import { type NextRequest, NextResponse } from 'next/server'
5-
import { v1CopilotChatContract } from '@/lib/api/contracts/v1/copilot'
6-
import { getValidationErrorMessage, parseRequest } from '@/lib/api/server'
7-
import { runHeadlessCopilotLifecycle } from '@/lib/copilot/request/lifecycle/headless'
1+
import { NextResponse } from 'next/server'
82
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
9-
import { getWorkflowById, resolveWorkflowIdForUser } from '@/lib/workflows/utils'
10-
import { authenticateRequest } from '@/app/api/v1/middleware'
11-
12-
export const maxDuration = 3600
13-
14-
const logger = createLogger('CopilotHeadlessAPI')
15-
const DEFAULT_COPILOT_MODEL = 'claude-opus-4-6'
163

174
/**
185
* POST /api/v1/copilot/chat
19-
* Headless copilot endpoint for server-side orchestration.
206
*
21-
* workflowId is optional - if not provided:
22-
* - If workflowName is provided, finds that workflow
23-
* - If exactly one workflow is available, uses that workflow as context
24-
* - Otherwise requires workflowId or workflowName to disambiguate
7+
* Deprecated: the v1 headless copilot chat API has been removed. The endpoint
8+
* returns 410 Gone for all callers.
259
*/
26-
export const POST = withRouteHandler(async (req: NextRequest) => {
27-
let messageId: string | undefined
28-
const authorized = await authenticateRequest(req, 'copilot-chat')
29-
if (authorized instanceof NextResponse) {
30-
return authorized
31-
}
32-
const { userId, rateLimit } = authorized
33-
const auth = {
34-
authenticated: true as const,
35-
userId,
36-
keyType: rateLimit.keyType,
37-
workspaceId: rateLimit.workspaceId,
38-
}
39-
40-
try {
41-
const parsedRequest = await parseRequest(
42-
v1CopilotChatContract,
43-
req,
44-
{},
45-
{
46-
validationErrorResponse: (error) =>
47-
NextResponse.json(
48-
{
49-
success: false,
50-
error: getValidationErrorMessage(error, 'Invalid request'),
51-
details: error.issues,
52-
},
53-
{ status: 400 }
54-
),
55-
invalidJsonResponse: () =>
56-
NextResponse.json({ success: false, error: 'Invalid request' }, { status: 400 }),
57-
}
58-
)
59-
if (!parsedRequest.success) return parsedRequest.response
60-
61-
const parsed = parsedRequest.data.body
62-
const selectedModel = parsed.model || DEFAULT_COPILOT_MODEL
63-
64-
// Resolve workflow ID
65-
const resolved = await resolveWorkflowIdForUser(
66-
auth.userId,
67-
parsed.workflowId,
68-
parsed.workflowName,
69-
auth.keyType === 'workspace' ? auth.workspaceId : undefined
70-
)
71-
if (resolved.status !== 'resolved') {
72-
return NextResponse.json(
73-
{
74-
success: false,
75-
error: resolved.message,
76-
},
77-
{ status: 400 }
78-
)
79-
}
80-
81-
if (auth.keyType === 'workspace' && auth.workspaceId) {
82-
const workflow = await getWorkflowById(resolved.workflowId)
83-
if (!workflow?.workspaceId || workflow.workspaceId !== auth.workspaceId) {
84-
return NextResponse.json(
85-
{ success: false, error: 'API key is not authorized for this workspace' },
86-
{ status: 403 }
87-
)
88-
}
89-
}
90-
91-
// Transform mode to transport mode (same as client API)
92-
// build and agent both map to 'agent' on the backend
93-
const effectiveMode = parsed.mode === 'agent' ? 'build' : parsed.mode
94-
const transportMode = effectiveMode === 'build' ? 'agent' : effectiveMode
95-
96-
// Always generate a chatId - required for artifacts system to work with subagents
97-
const chatId = parsed.chatId || generateId()
98-
99-
messageId = generateId()
100-
logger.info(
101-
messageId
102-
? `Received headless copilot chat start request [messageId:${messageId}]`
103-
: 'Received headless copilot chat start request',
104-
{
105-
workflowId: resolved.workflowId,
106-
workflowName: parsed.workflowName,
107-
chatId,
108-
mode: transportMode,
109-
autoExecuteTools: parsed.autoExecuteTools,
110-
timeout: parsed.timeout,
111-
}
112-
)
113-
const requestPayload = {
114-
message: parsed.message,
115-
workflowId: resolved.workflowId,
116-
userId: auth.userId,
117-
model: selectedModel,
118-
mode: transportMode,
119-
messageId,
120-
chatId,
121-
}
122-
123-
const result = await runHeadlessCopilotLifecycle(requestPayload, {
124-
userId: auth.userId,
125-
workflowId: resolved.workflowId,
126-
chatId,
127-
goRoute: '/api/mcp',
128-
autoExecuteTools: parsed.autoExecuteTools,
129-
timeout: parsed.timeout,
130-
interactive: false,
131-
})
132-
133-
return NextResponse.json({
134-
success: result.success,
135-
content: result.content,
136-
toolCalls: result.toolCalls,
137-
chatId: result.chatId || chatId,
138-
error: result.error,
139-
})
140-
} catch (error) {
141-
logger.error(
142-
messageId
143-
? `Headless copilot request failed [messageId:${messageId}]`
144-
: 'Headless copilot request failed',
145-
{
146-
error: toError(error).message,
147-
}
148-
)
149-
return NextResponse.json({ success: false, error: 'Internal server error' }, { status: 500 })
150-
}
151-
})
10+
export const POST = withRouteHandler(async () =>
11+
NextResponse.json(
12+
{
13+
success: false,
14+
error: 'The v1 copilot chat API has been deprecated and is no longer available.',
15+
},
16+
{ status: 410, headers: { 'Cache-Control': 'no-store' } }
17+
)
18+
)

packages/db/.env.swp

12 KB
Binary file not shown.

scripts/check-api-validation-contracts.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ const INDIRECT_ZOD_ROUTES = new Set([
8888
'apps/sim/app/api/mcp/copilot/route.ts',
8989
'apps/sim/app/api/mcp/copilot/.well-known/oauth-authorization-server/route.ts',
9090
'apps/sim/app/api/mcp/copilot/.well-known/oauth-protected-resource/route.ts',
91+
// Deprecated v1 headless copilot chat API: gated to always return 410 Gone
92+
// and consumes no client-supplied input.
93+
'apps/sim/app/api/v1/copilot/chat/route.ts',
9194
])
9295

9396
/**

0 commit comments

Comments
 (0)