Files
simstudioai--sim/apps/sim/lib/api/contracts/logs.ts
T
wehub-resource-sync d25d482dc2
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:20:55 +08:00

404 lines
12 KiB
TypeScript

import { z } from 'zod'
import { userFileSchema } from '@/lib/api/contracts/primitives'
import { defineRouteContract } from '@/lib/api/contracts/types'
const comparisonOperatorSchema = z.enum(['=', '>', '<', '>=', '<=', '!='])
export const logIdParamsSchema = z.object({
id: z.string().min(1),
})
export const executionIdParamsSchema = z.object({
executionId: z.string().min(1),
})
export const cancelWorkflowExecutionParamsSchema = z.object({
id: z.string().min(1, 'Invalid workflow ID'),
executionId: z.string().min(1, 'Invalid execution ID'),
})
const logFilterQuerySchema = z.object({
workspaceId: z.string(),
level: z.string().optional(),
workflowIds: z.string().optional(),
folderIds: z.string().optional(),
triggers: z.string().optional(),
startDate: z.string().optional(),
endDate: z.string().optional(),
search: z.string().optional(),
workflowName: z.string().optional(),
folderName: z.string().optional(),
executionId: z.string().optional(),
costOperator: comparisonOperatorSchema.optional(),
costValue: z.coerce.number().optional(),
durationOperator: comparisonOperatorSchema.optional(),
durationValue: z.coerce.number().optional(),
})
export const logSortBySchema = z.enum(['date', 'duration', 'cost', 'status']).default('date')
export const logSortOrderSchema = z.enum(['asc', 'desc']).default('desc')
export const listLogsQuerySchema = logFilterQuerySchema.extend({
cursor: z.string().optional(),
limit: z.coerce.number().int().min(1).max(200).optional().default(100),
sortBy: logSortBySchema,
sortOrder: logSortOrderSchema,
})
export const logDetailQuerySchema = z.object({
workspaceId: z.string().min(1),
})
export const statsQueryParamsSchema = logFilterQuerySchema.extend({
segmentCount: z.coerce.number().optional().default(72),
})
const workflowSummarySchema = z
.object({
id: z.string(),
name: z.string().nullable(),
description: z.string().nullable(),
folderId: z.string().nullable(),
userId: z.string().nullable(),
workspaceId: z.string().nullable(),
createdAt: z.string().nullable(),
updatedAt: z.string().nullable(),
})
.partial()
const tokenBreakdownSchema = z
.object({
total: z.number().optional(),
input: z.number().optional(),
output: z.number().optional(),
prompt: z.number().optional(),
completion: z.number().optional(),
})
.partial()
const modelCostSchema = z
.object({
input: z.number().optional(),
output: z.number().optional(),
total: z.number().optional(),
tokens: tokenBreakdownSchema.optional(),
})
.partial()
const costSummarySchema = z
.object({
total: z.number().optional(),
input: z.number().optional(),
output: z.number().optional(),
tokens: tokenBreakdownSchema.optional(),
models: z.record(z.string(), modelCostSchema).optional(),
pricing: z
.object({
input: z.number(),
output: z.number(),
cachedInput: z.number().optional(),
updatedAt: z.string(),
})
.optional(),
})
.partial()
/**
* Itemized cost breakdown derived from the usage_log ledger (the single source
* of truth) for the detail view. Each item is one billed line (base fee, a
* model, or a tool/integration); the items reconcile to `total`.
*/
const costLedgerItemSchema = z.object({
category: z.enum(['fixed', 'model', 'tool']),
description: z.string(),
cost: z.number(),
inputTokens: z.number().optional(),
outputTokens: z.number().optional(),
})
export const costLedgerSchema = z.object({
total: z.number(),
items: z.array(costLedgerItemSchema),
})
export type CostLedger = z.output<typeof costLedgerSchema>
export type CostLedgerItem = z.output<typeof costLedgerItemSchema>
const pauseSummarySchema = z.object({
status: z.string().nullable(),
total: z.number(),
resumed: z.number(),
})
const blockExecutionSchema = z.object({
id: z.string(),
blockId: z.string(),
blockName: z.string(),
blockType: z.string(),
startedAt: z.string(),
endedAt: z.string(),
durationMs: z.number(),
status: z.enum(['success', 'error', 'skipped']),
errorMessage: z.string().optional(),
errorStackTrace: z.string().optional(),
inputData: z.unknown(),
outputData: z.unknown(),
cost: costSummarySchema.optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
})
const toolCallSchema = z
.object({
id: z.string().optional(),
name: z.string().optional(),
arguments: z.unknown().optional(),
result: z.unknown().optional(),
error: z.string().optional(),
startTime: z.string().optional(),
endTime: z.string().optional(),
duration: z.number().optional(),
})
.passthrough()
type TraceSpan = {
id: string
name: string
type: string
duration?: number
durationMs?: number
startTime?: string
endTime?: string
status?: string
blockId?: string
input?: unknown
output?: unknown
tokens?: number | { total?: number; input?: number; output?: number }
relativeStartMs?: number
toolCalls?: Array<z.output<typeof toolCallSchema>>
children?: TraceSpan[]
}
const traceSpanSchema: z.ZodType<TraceSpan> = z.lazy(() =>
z
.object({
id: z.string(),
name: z.string(),
type: z.string(),
duration: z.number().optional(),
durationMs: z.number().optional(),
startTime: z.string().optional(),
endTime: z.string().optional(),
status: z.string().optional(),
blockId: z.string().optional(),
input: z.unknown().optional(),
output: z.unknown().optional(),
tokens: z
.union([
z.number(),
z
.object({
total: z.number().optional(),
input: z.number().optional(),
output: z.number().optional(),
})
.partial(),
])
.optional(),
relativeStartMs: z.number().optional(),
toolCalls: z.array(toolCallSchema).optional(),
children: z.array(traceSpanSchema).optional(),
})
.passthrough()
)
const executionDataDetailSchema = z
.object({
totalDuration: z.number().nullable().optional(),
enhanced: z.literal(true).optional(),
traceSpans: z.array(traceSpanSchema).optional(),
blockExecutions: z.array(blockExecutionSchema).optional(),
finalOutput: z.unknown().optional(),
workflowInput: z.unknown().optional(),
blockInput: z.record(z.string(), z.unknown()).optional(),
trigger: z.unknown().optional(),
})
.passthrough()
export const workflowLogSummarySchema = z.object({
id: z.string(),
workflowId: z.string().nullable(),
executionId: z.string().nullable(),
deploymentVersionId: z.string().nullable(),
deploymentVersion: z.number().nullable(),
deploymentVersionName: z.string().nullable(),
level: z.string(),
status: z.string().nullable(),
duration: z.string().nullable(),
trigger: z.string().nullable(),
createdAt: z.string(),
workflow: workflowSummarySchema.nullable(),
jobTitle: z.string().nullable(),
// Top-level run cost is the cost_total projection of the usage_log ledger,
// rendered as { total } (dollars). The itemized breakdown lives in costLedger
// (detail only); per-block costs use the richer costSummarySchema elsewhere.
cost: z.object({ total: z.number() }).nullable(),
pauseSummary: pauseSummarySchema,
hasPendingPause: z.boolean(),
})
export const workflowLogDetailSchema = workflowLogSummarySchema.extend({
executionData: executionDataDetailSchema,
files: z.array(userFileSchema).nullable(),
// Itemized, ledger-sourced cost breakdown. Null for legacy/pre-ledger runs,
// where the UI falls back to the (reconciling) cost jsonb.
costLedger: costLedgerSchema.nullable().optional(),
})
export type WorkflowLogSummary = z.output<typeof workflowLogSummarySchema>
export type WorkflowLogDetail = z.output<typeof workflowLogDetailSchema>
/**
* A row that may be either a list-view summary or a fully loaded detail. Used by
* UI surfaces that render the same log before and after its detail query resolves.
*/
export type WorkflowLogRow = WorkflowLogSummary &
Partial<Pick<WorkflowLogDetail, 'executionData' | 'files' | 'costLedger'>>
export const listLogsResponseSchema = z.object({
data: z.array(workflowLogSummarySchema),
nextCursor: z.string().nullable(),
})
export type ListLogsResponse = z.output<typeof listLogsResponseSchema>
export const segmentStatsSchema = z.object({
timestamp: z.string(),
totalExecutions: z.number(),
successfulExecutions: z.number(),
avgDurationMs: z.number(),
})
export const workflowStatsSchema = z.object({
workflowId: z.string(),
workflowName: z.string(),
segments: z.array(segmentStatsSchema),
overallSuccessRate: z.number(),
totalExecutions: z.number(),
totalSuccessful: z.number(),
})
export const dashboardStatsResponseSchema = z.object({
workflows: z.array(workflowStatsSchema),
aggregateSegments: z.array(segmentStatsSchema),
totalRuns: z.number(),
totalErrors: z.number(),
avgLatency: z.number(),
timeBounds: z.object({
start: z.string(),
end: z.string(),
}),
segmentMs: z.number(),
})
export const executionSnapshotDataSchema = z.object({
executionId: z.string(),
workflowId: z.string().nullable(),
workflowState: z.record(z.string(), z.unknown()).nullable(),
childWorkflowSnapshots: z.record(z.string(), z.unknown()).optional(),
executionMetadata: z.object({
trigger: z.string().nullable(),
startedAt: z.string(),
endedAt: z.string().optional(),
totalDurationMs: z.number().nullable().optional(),
cost: z.unknown().nullable(),
totalTokens: z.number().nullable().optional(),
}),
})
export const triggersQuerySchema = z.object({
workspaceId: z.string(),
})
export type TriggersQuery = z.output<typeof triggersQuerySchema>
export const cancelWorkflowExecutionResponseSchema = z.object({
success: z.boolean(),
executionId: z.string(),
redisAvailable: z.boolean(),
durablyRecorded: z.boolean(),
locallyAborted: z.boolean(),
pausedCancelled: z.boolean(),
reason: z.enum(['recorded', 'redis_unavailable', 'redis_write_failed']),
})
export type SegmentStats = z.output<typeof segmentStatsSchema>
export type WorkflowStats = z.output<typeof workflowStatsSchema>
export type DashboardStatsResponse = z.output<typeof dashboardStatsResponseSchema>
export type ExecutionSnapshotData = z.output<typeof executionSnapshotDataSchema>
export type CancelWorkflowExecutionResponse = z.output<typeof cancelWorkflowExecutionResponseSchema>
export const listLogsContract = defineRouteContract({
method: 'GET',
path: '/api/logs',
query: listLogsQuerySchema,
response: {
mode: 'json',
schema: listLogsResponseSchema,
},
})
export const getLogDetailContract = defineRouteContract({
method: 'GET',
path: '/api/logs/[id]',
params: logIdParamsSchema,
query: logDetailQuerySchema,
response: {
mode: 'json',
schema: z.object({
data: workflowLogDetailSchema,
}),
},
})
export const getLogByExecutionIdContract = defineRouteContract({
method: 'GET',
path: '/api/logs/by-execution/[executionId]',
params: executionIdParamsSchema,
query: logDetailQuerySchema,
response: {
mode: 'json',
schema: z.object({
data: workflowLogDetailSchema,
}),
},
})
export const getDashboardStatsContract = defineRouteContract({
method: 'GET',
path: '/api/logs/stats',
query: statsQueryParamsSchema,
response: {
mode: 'json',
schema: dashboardStatsResponseSchema,
},
})
export const getExecutionSnapshotContract = defineRouteContract({
method: 'GET',
path: '/api/logs/execution/[executionId]',
params: executionIdParamsSchema,
response: {
mode: 'json',
schema: executionSnapshotDataSchema,
},
})
export const cancelWorkflowExecutionContract = defineRouteContract({
method: 'POST',
path: '/api/workflows/[id]/executions/[executionId]/cancel',
params: cancelWorkflowExecutionParamsSchema,
response: {
mode: 'json',
schema: cancelWorkflowExecutionResponseSchema,
},
})