Skip to content

Commit 8c9903f

Browse files
committed
improvement(mothership): structure sim side typing
1 parent cce740d commit 8c9903f

60 files changed

Lines changed: 5399 additions & 1833 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/sim/app/api/copilot/confirm/route.test.ts

Lines changed: 44 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -161,27 +161,33 @@ describe('Copilot Confirm API Route', () => {
161161
)
162162
})
163163

164-
it('uses upsertAsyncToolCall for non-terminal confirmations', async () => {
164+
it('accepts primitive terminal confirmation data', async () => {
165165
const response = await POST(
166166
createMockPostRequest({
167167
toolCallId: 'tool-call-123',
168-
status: 'accepted',
168+
status: 'success',
169+
message: 'Tool executed successfully',
170+
data: 'done',
169171
})
170172
)
171173

172174
expect(response.status).toBe(200)
173-
expect(upsertAsyncToolCall).toHaveBeenCalledWith({
174-
runId: 'run-1',
175-
checkpointId: 'checkpoint-1',
175+
expect(completeAsyncToolCall).toHaveBeenCalledWith({
176176
toolCallId: 'tool-call-123',
177-
toolName: 'client_tool',
178-
args: { foo: 'bar' },
179-
status: 'pending',
177+
status: 'completed',
178+
result: 'done',
179+
error: null,
180180
})
181-
expect(completeAsyncToolCall).not.toHaveBeenCalled()
181+
expect(publishToolConfirmation).toHaveBeenCalledWith(
182+
expect.objectContaining({
183+
toolCallId: 'tool-call-123',
184+
status: 'success',
185+
data: 'done',
186+
})
187+
)
182188
})
183189

184-
it('publishes confirmation after a durable non-terminal update', async () => {
190+
it('keeps background as a live pending detach confirmation', async () => {
185191
const response = await POST(
186192
createMockPostRequest({
187193
toolCallId: 'tool-call-123',
@@ -190,14 +196,8 @@ describe('Copilot Confirm API Route', () => {
190196
)
191197

192198
expect(response.status).toBe(200)
193-
expect(upsertAsyncToolCall).toHaveBeenCalledWith({
194-
runId: 'run-1',
195-
checkpointId: 'checkpoint-1',
196-
toolCallId: 'tool-call-123',
197-
toolName: 'client_tool',
198-
args: { foo: 'bar' },
199-
status: 'pending',
200-
})
199+
expect(upsertAsyncToolCall).not.toHaveBeenCalled()
200+
expect(completeAsyncToolCall).not.toHaveBeenCalled()
201201
expect(publishToolConfirmation).toHaveBeenCalledWith(
202202
expect.objectContaining({
203203
toolCallId: 'tool-call-123',
@@ -206,6 +206,32 @@ describe('Copilot Confirm API Route', () => {
206206
)
207207
})
208208

209+
it('rejects unsupported accepted and rejected confirmation statuses', async () => {
210+
const acceptedResponse = await POST(
211+
createMockPostRequest({
212+
toolCallId: 'tool-call-123',
213+
status: 'accepted',
214+
})
215+
)
216+
217+
expect(acceptedResponse.status).toBe(400)
218+
expect(await acceptedResponse.json()).toEqual({
219+
error: 'Invalid request data: Invalid notification status',
220+
})
221+
222+
const rejectedResponse = await POST(
223+
createMockPostRequest({
224+
toolCallId: 'tool-call-123',
225+
status: 'rejected',
226+
})
227+
)
228+
229+
expect(rejectedResponse.status).toBe(400)
230+
expect(await rejectedResponse.json()).toEqual({
231+
error: 'Invalid request data: Invalid notification status',
232+
})
233+
})
234+
209235
it('returns 400 when the durable write fails before publish', async () => {
210236
completeAsyncToolCall.mockRejectedValueOnce(new Error('db down'))
211237

apps/sim/app/api/copilot/confirm/route.ts

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import { createLogger } from '@sim/logger'
22
import { type NextRequest, NextResponse } from 'next/server'
33
import { z } from 'zod'
4-
import { ASYNC_TOOL_STATUS } from '@/lib/copilot/async-runs/lifecycle'
4+
import {
5+
ASYNC_TOOL_CONFIRMATION_STATUS,
6+
ASYNC_TOOL_STATUS,
7+
type AsyncCompletionData,
8+
type AsyncConfirmationStatus,
9+
} from '@/lib/copilot/async-runs/lifecycle'
510
import {
611
completeAsyncToolCall,
712
getAsyncToolCall,
@@ -16,37 +21,55 @@ import {
1621
createNotFoundResponse,
1722
createRequestTracker,
1823
createUnauthorizedResponse,
19-
type NotificationStatus,
2024
} from '@/lib/copilot/request/http'
2125

2226
const logger = createLogger('CopilotConfirmAPI')
2327

2428
// Schema for confirmation request
2529
const ConfirmationSchema = z.object({
2630
toolCallId: z.string().min(1, 'Tool call ID is required'),
27-
status: z.enum(['success', 'error', 'accepted', 'rejected', 'background', 'cancelled'] as const, {
28-
errorMap: () => ({ message: 'Invalid notification status' }),
29-
}),
31+
status: z.enum(
32+
Object.values(ASYNC_TOOL_CONFIRMATION_STATUS) as [
33+
AsyncConfirmationStatus,
34+
...AsyncConfirmationStatus[],
35+
],
36+
{
37+
errorMap: () => ({ message: 'Invalid notification status' }),
38+
}
39+
),
3040
message: z.string().optional(),
31-
data: z.record(z.unknown()).optional(),
41+
data: z.unknown().optional(),
3242
})
3343

3444
/**
35-
* Persist the durable tool status, then publish a wakeup event.
45+
* Persist terminal durable tool status, then publish a wakeup event.
46+
*
47+
* `background` remains a live detach signal in the current browser workflow
48+
* runtime, so it should not rewrite the durable async row.
3649
*/
3750
async function updateToolCallStatus(
3851
existing: NonNullable<Awaited<ReturnType<typeof getAsyncToolCall>>>,
39-
status: NotificationStatus,
52+
status: AsyncConfirmationStatus,
4053
message?: string,
41-
data?: Record<string, unknown>
54+
data?: AsyncCompletionData
4255
): Promise<boolean> {
4356
const toolCallId = existing.toolCallId
57+
if (status === ASYNC_TOOL_CONFIRMATION_STATUS.background) {
58+
publishToolConfirmation({
59+
toolCallId,
60+
status,
61+
message: message || undefined,
62+
timestamp: new Date().toISOString(),
63+
data,
64+
})
65+
return true
66+
}
4467
const durableStatus =
4568
status === 'success'
4669
? ASYNC_TOOL_STATUS.completed
4770
: status === 'cancelled'
4871
? ASYNC_TOOL_STATUS.cancelled
49-
: status === 'error' || status === 'rejected'
72+
: status === 'error'
5073
? ASYNC_TOOL_STATUS.failed
5174
: ASYNC_TOOL_STATUS.pending
5275
try {
@@ -71,12 +94,11 @@ async function updateToolCallStatus(
7194
status: durableStatus,
7295
})
7396
}
74-
const timestamp = new Date().toISOString()
7597
publishToolConfirmation({
7698
toolCallId,
7799
status,
78100
message: message || undefined,
79-
timestamp,
101+
timestamp: new Date().toISOString(),
80102
data,
81103
})
82104
return true
@@ -92,7 +114,7 @@ async function updateToolCallStatus(
92114

93115
/**
94116
* POST /api/copilot/confirm
95-
* Update tool call status (Accept/Reject)
117+
* Accept client tool completion or detach confirmations.
96118
*/
97119
export async function POST(req: NextRequest) {
98120
const tracker = createRequestTracker()

0 commit comments

Comments
 (0)