Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM oven/bun:1.3.11-alpine
FROM oven/bun:1.3.13-alpine

# Install necessary packages for development
RUN apk add --no-cache \
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/docs-embeddings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13

- name: Setup Node
uses: actions/setup-node@v4
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/i18n.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13

- name: Cache Bun dependencies
uses: actions/cache@v4
Expand Down Expand Up @@ -122,7 +122,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13

- name: Cache Bun dependencies
uses: actions/cache@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/migrations.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13

- name: Cache Bun dependencies
uses: actions/cache@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-cli.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13

- name: Setup Node.js for npm publishing
uses: actions/setup-node@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish-ts-sdk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13

- name: Setup Node.js for npm publishing
uses: actions/setup-node@v4
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11
bun-version: 1.3.13

- name: Setup Node
uses: actions/setup-node@v4
Expand Down
619 changes: 431 additions & 188 deletions apps/docs/content/docs/en/tools/ashby.mdx

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions apps/docs/content/docs/en/triggers/ashby.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,14 @@ Trigger workflow when a candidate is hired
| ↳ `job` | object | job output from the tool |
| ↳ `id` | string | Job UUID |
| ↳ `title` | string | Job title |
| `offer` | object | offer output from the tool |
| ↳ `id` | string | Accepted offer UUID |
| ↳ `applicationId` | string | Associated application UUID |
| ↳ `acceptanceStatus` | string | Offer acceptance status |
| ↳ `offerStatus` | string | Offer process status |
| ↳ `decidedAt` | string | Offer decision timestamp \(ISO 8601\) |
| ↳ `latestVersion` | object | latestVersion output from the tool |
| ↳ `id` | string | Latest offer version UUID |


---
Expand Down
6 changes: 3 additions & 3 deletions apps/sim/app/(landing)/integrations/data/integrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -1031,7 +1031,7 @@
},
{
"name": "List Applications",
"description": "Lists all applications in an Ashby organization with pagination and optional filters for status, job, candidate, and creation date."
"description": "Lists all applications in an Ashby organization with pagination and optional filters for status, job, and creation date."
},
{
"name": "Get Application",
Expand All @@ -1051,11 +1051,11 @@
},
{
"name": "Add Candidate Tag",
"description": "Adds a tag to a candidate in Ashby."
"description": "Adds a tag to a candidate in Ashby and returns the updated candidate."
},
{
"name": "Remove Candidate Tag",
"description": "Removes a tag from a candidate in Ashby."
"description": "Removes a tag from a candidate in Ashby and returns the updated candidate."
},
{
"name": "Get Offer",
Expand Down
2 changes: 2 additions & 0 deletions apps/sim/app/api/copilot/chat/stream/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ vi.mock('@/lib/copilot/request/session', () => ({
}),
encodeSSEEnvelope: (event: Record<string, unknown>) =>
new TextEncoder().encode(`data: ${JSON.stringify(event)}\n\n`),
encodeSSEComment: (comment: string) => new TextEncoder().encode(`: ${comment}\n\n`),
SSE_RESPONSE_HEADERS: {
'Content-Type': 'text/event-stream',
},
Expand Down Expand Up @@ -132,6 +133,7 @@ describe('copilot chat stream replay route', () => {
)

const chunks = await readAllChunks(response)
expect(chunks[0]).toBe(': accepted\n\n')
expect(chunks.join('')).toContain(
JSON.stringify({
status: MothershipStreamV1CompletionStatus.cancelled,
Expand Down
45 changes: 37 additions & 8 deletions apps/sim/app/api/copilot/chat/stream/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { getCopilotTracer, markSpanForError } from '@/lib/copilot/request/otel'
import {
checkForReplayGap,
createEvent,
encodeSSEComment,
encodeSSEEnvelope,
readEvents,
readFilePreviewSessions,
Expand All @@ -31,6 +32,7 @@ export const maxDuration = 3600

const logger = createLogger('CopilotChatStreamAPI')
const POLL_INTERVAL_MS = 250
const REPLAY_KEEPALIVE_INTERVAL_MS = 15_000
const MAX_STREAM_MS = 60 * 60 * 1000

function extractCanonicalRequestId(value: unknown): string {
Expand Down Expand Up @@ -266,6 +268,7 @@ async function handleResumeRequestBody({
let controllerClosed = false
let sawTerminalEvent = false
let currentRequestId = extractRunRequestId(run)
let lastWriteTime = Date.now()
// Stamp the logical request id + chat id on the resume root as soon
// as we resolve them from the run row, so TraceQL joins work on
// resume legs the same way they do on the original POST.
Expand All @@ -291,6 +294,19 @@ async function handleResumeRequestBody({
if (controllerClosed) return false
try {
controller.enqueue(encodeSSEEnvelope(payload))
lastWriteTime = Date.now()
return true
} catch {
controllerClosed = true
return false
}
}

const enqueueComment = (comment: string) => {
if (controllerClosed) return false
try {
controller.enqueue(encodeSSEComment(comment))
lastWriteTime = Date.now()
return true
} catch {
controllerClosed = true
Expand All @@ -306,22 +322,22 @@ async function handleResumeRequestBody({
const flushEvents = async () => {
const events = await readEvents(streamId, cursor)
if (events.length > 0) {
totalEventsFlushed += events.length
logger.debug('[Resume] Flushing events', {
streamId,
afterCursor: cursor,
eventCount: events.length,
})
}
for (const envelope of events) {
if (!enqueueEvent(envelope)) {
break
}
totalEventsFlushed += 1
cursor = envelope.stream.cursor ?? String(envelope.seq)
currentRequestId = extractEnvelopeRequestId(envelope) || currentRequestId
if (envelope.type === MothershipStreamV1EventType.complete) {
sawTerminalEvent = true
}
if (!enqueueEvent(envelope)) {
break
}
}
}

Expand All @@ -341,21 +357,30 @@ async function handleResumeRequestBody({
reason: options?.reason,
requestId: currentRequestId,
})) {
if (!enqueueEvent(envelope)) {
break
}
cursor = envelope.stream.cursor ?? String(envelope.seq)
if (envelope.type === MothershipStreamV1EventType.complete) {
sawTerminalEvent = true
}
if (!enqueueEvent(envelope)) {
break
}
}
}

try {
enqueueComment('accepted')

const gap = await checkForReplayGap(streamId, afterCursor, currentRequestId)
if (gap) {
for (const envelope of gap.envelopes) {
enqueueEvent(envelope)
if (!enqueueEvent(envelope)) {
break
}
cursor = envelope.stream.cursor ?? String(envelope.seq)
currentRequestId = extractEnvelopeRequestId(envelope) || currentRequestId
if (envelope.type === MothershipStreamV1EventType.complete) {
sawTerminalEvent = true
}
}
return
}
Expand Down Expand Up @@ -408,6 +433,10 @@ async function handleResumeRequestBody({
break
}

if (Date.now() - lastWriteTime >= REPLAY_KEEPALIVE_INTERVAL_MS) {
enqueueComment('keepalive')
}

await sleep(POLL_INTERVAL_MS)
}
if (!controllerClosed && Date.now() - startTime >= MAX_STREAM_MS) {
Expand Down
26 changes: 18 additions & 8 deletions apps/sim/app/api/table/[tableId]/rows/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ const QueryRowsSchema = z.object({
.min(0, 'Offset must be 0 or greater')
.optional()
.default(0),
includeTotal: z
.preprocess(
(val) => (val === null || val === undefined || val === '' ? undefined : val === 'true'),
z.boolean().optional()
)
.default(true),
})

const nonEmptyFilter = z
Expand Down Expand Up @@ -328,6 +334,7 @@ export const GET = withRouteHandler(
const sortParam = searchParams.get('sort')
const limit = searchParams.get('limit')
const offset = searchParams.get('offset')
const includeTotalParam = searchParams.get('includeTotal')

let filter: Record<string, unknown> | undefined
let sort: Sort | undefined
Expand All @@ -349,6 +356,7 @@ export const GET = withRouteHandler(
sort,
limit,
offset,
includeTotal: includeTotalParam,
})

const accessResult = await checkAccess(tableId, authResult.userId, 'read')
Expand Down Expand Up @@ -398,17 +406,19 @@ export const GET = withRouteHandler(
query = query.orderBy(userTableRows.position) as typeof query
}

const countQuery = db
.select({ count: sql<number>`count(*)` })
.from(userTableRows)
.where(and(...baseConditions))

const [{ count: totalCount }] = await countQuery
let totalCount: number | null = null
if (validated.includeTotal) {
const [{ count }] = await db
.select({ count: sql<number>`count(*)` })
.from(userTableRows)
.where(and(...baseConditions))
totalCount = Number(count)
}

const rows = await query.limit(validated.limit).offset(validated.offset)

logger.info(
`[${requestId}] Queried ${rows.length} rows from table ${tableId} (total: ${totalCount})`
`[${requestId}] Queried ${rows.length} rows from table ${tableId} (total: ${totalCount ?? 'n/a'})`
)

return NextResponse.json({
Expand All @@ -424,7 +434,7 @@ export const GET = withRouteHandler(
r.updatedAt instanceof Date ? r.updatedAt.toISOString() : String(r.updatedAt),
})),
rowCount: rows.length,
totalCount: Number(totalCount),
totalCount,
limit: validated.limit,
offset: validated.offset,
},
Expand Down
48 changes: 38 additions & 10 deletions apps/sim/app/api/v1/tables/[tableId]/rows/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ const QueryRowsSchema = z.object({
.optional()
)
.default(0),
includeTotal: z
.preprocess(
(val) => (val === null || val === undefined || val === '' ? undefined : val === 'true'),
z.boolean().optional()
)
.default(true),
})

const nonEmptyFilter = z
Expand Down Expand Up @@ -219,6 +225,7 @@ export const GET = withRouteHandler(
sort,
limit: searchParams.get('limit'),
offset: searchParams.get('offset'),
includeTotal: searchParams.get('includeTotal'),
})

const scopeError = checkWorkspaceScope(rateLimit, validated.workspaceId)
Expand Down Expand Up @@ -268,16 +275,37 @@ export const GET = withRouteHandler(
query = query.orderBy(userTableRows.position) as typeof query
}

const countQuery = db
.select({ count: sql<number>`count(*)` })
.from(userTableRows)
.where(and(...baseConditions))
const rowsPromise = query.limit(validated.limit).offset(validated.offset)

let totalCount: number | null = null
if (validated.includeTotal) {
const countQuery = db
.select({ count: sql<number>`count(*)` })
.from(userTableRows)
.where(and(...baseConditions))
const [countResult, rows] = await Promise.all([countQuery, rowsPromise])
totalCount = Number(countResult[0].count)
return NextResponse.json({
success: true,
data: {
rows: rows.map((r) => ({
id: r.id,
data: r.data,
position: r.position,
createdAt:
r.createdAt instanceof Date ? r.createdAt.toISOString() : String(r.createdAt),
updatedAt:
r.updatedAt instanceof Date ? r.updatedAt.toISOString() : String(r.updatedAt),
})),
rowCount: rows.length,
totalCount,
limit: validated.limit,
offset: validated.offset,
},
})
}

const [countResult, rows] = await Promise.all([
countQuery,
query.limit(validated.limit).offset(validated.offset),
])
const totalCount = countResult[0].count
const rows = await rowsPromise

return NextResponse.json({
success: true,
Expand All @@ -292,7 +320,7 @@ export const GET = withRouteHandler(
r.updatedAt instanceof Date ? r.updatedAt.toISOString() : String(r.updatedAt),
})),
rowCount: rows.length,
totalCount: Number(totalCount),
totalCount,
limit: validated.limit,
offset: validated.offset,
},
Expand Down
Loading
Loading