chore: import upstream snapshot with attribution
Publish CLI Package / publish-npm (push) Waiting to run
Publish Python SDK / publish-pypi (push) Waiting to run
Publish TypeScript SDK / publish-npm (push) Waiting to run
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

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
@@ -0,0 +1,96 @@
import { generateId } from '@sim/utils/id'
import type {
TemporalCancelWorkflowParams,
TemporalCancelWorkflowResponse,
} from '@/tools/temporal/types'
import {
parseTemporalResponse,
TEMPORAL_CLIENT_IDENTITY,
temporalRequestHeaders,
temporalWorkflowUrl,
workflowExecutionRef,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const cancelWorkflowTool: ToolConfig<
TemporalCancelWorkflowParams,
TemporalCancelWorkflowResponse
> = {
id: 'temporal_cancel_workflow',
name: 'Temporal Cancel Workflow',
description:
'Request cooperative cancellation of a running Temporal workflow execution. The workflow decides how to respond to the request.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
workflowId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow ID of the execution to cancel',
},
runId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Run ID of a specific run to cancel (defaults to the latest run)',
},
reason: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Reason for the cancellation, recorded in the workflow history',
},
},
request: {
url: (params) =>
`${temporalWorkflowUrl(params.serverUrl, params.namespace, params.workflowId)}/cancel`,
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => {
const body: Record<string, unknown> = {
workflowExecution: workflowExecutionRef(params.workflowId, params.runId),
identity: TEMPORAL_CLIENT_IDENTITY,
requestId: generateId(),
}
if (params.reason) body.reason = params.reason
return body
},
},
transformResponse: async (response: Response, params) => {
await parseTemporalResponse(response, 'cancel workflow')
return {
success: true,
output: {
workflowId: params?.workflowId ?? '',
},
}
},
outputs: {
workflowId: {
type: 'string',
description: 'Workflow ID of the execution whose cancellation was requested',
},
},
}
@@ -0,0 +1,93 @@
import type {
TemporalCountWorkflowsParams,
TemporalCountWorkflowsResponse,
} from '@/tools/temporal/types'
import {
decodePayload,
parseTemporalResponse,
type TemporalPayload,
temporalNamespaceUrl,
temporalRequestHeaders,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const countWorkflowsTool: ToolConfig<
TemporalCountWorkflowsParams,
TemporalCountWorkflowsResponse
> = {
id: 'temporal_count_workflows',
name: 'Temporal Count Workflows',
description:
'Count workflow executions in a Temporal namespace matching a visibility query, with optional GROUP BY aggregation.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Visibility count filter, e.g. ExecutionStatus = "Running" or ... GROUP BY ExecutionStatus (empty counts all executions)',
},
},
request: {
url: (params) => {
const base = `${temporalNamespaceUrl(params.serverUrl, params.namespace)}/workflow-count`
return params.query ? `${base}?query=${encodeURIComponent(params.query)}` : base
},
method: 'GET',
headers: (params) => temporalRequestHeaders(params),
},
transformResponse: async (response: Response) => {
const data = await parseTemporalResponse<{
count?: string
groups?: Array<{ groupValues?: TemporalPayload[]; count?: string }>
}>(response, 'count workflows')
return {
success: true,
output: {
count: data.count != null ? Number(data.count) : 0,
groups: (data.groups ?? []).map((group) => ({
values: (group.groupValues ?? []).map(decodePayload),
count: group.count != null ? Number(group.count) : 0,
})),
},
}
},
outputs: {
count: { type: 'number', description: 'Number of workflow executions matching the query' },
groups: {
type: 'array',
description: 'Per-group counts when the query uses GROUP BY (empty otherwise)',
items: {
type: 'object',
properties: {
values: { type: 'json', description: 'Decoded values of the GROUP BY fields' },
count: { type: 'number', description: 'Number of executions in the group' },
},
},
},
},
}
+175
View File
@@ -0,0 +1,175 @@
import { generateId } from '@sim/utils/id'
import type {
TemporalCreateScheduleParams,
TemporalScheduleMutationResponse,
} from '@/tools/temporal/types'
import {
parseJsonArgs,
parseTemporalResponse,
TEMPORAL_CLIENT_IDENTITY,
temporalRequestHeaders,
temporalScheduleUrl,
toDurationString,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const createScheduleTool: ToolConfig<
TemporalCreateScheduleParams,
TemporalScheduleMutationResponse
> = {
id: 'temporal_create_schedule',
name: 'Temporal Create Schedule',
description: 'Create a Temporal schedule that starts a workflow on a cron or interval cadence.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
scheduleId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Unique ID for the new schedule (e.g., nightly-report)',
},
workflowId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Workflow ID for started workflows (the schedule appends the run time to keep IDs unique)',
},
workflowType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Registered workflow type name the schedule starts (e.g., ReportWorkflow)',
},
taskQueue: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Task queue the workflow worker polls (e.g., reports)',
},
input: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Workflow input as JSON. A top-level array is passed as the argument list (one argument per element); any other value is passed as a single argument',
},
cronExpressions: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Cron expressions defining when the schedule fires, comma- or newline-separated for multiple (e.g., "0 12 * * *"). At least one of cronExpressions or intervalSeconds is required',
},
intervalSeconds: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description:
'Fixed interval between actions in seconds. At least one of cronExpressions or intervalSeconds is required',
},
timezone: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'IANA time zone for cron evaluation (e.g., America/New_York; defaults to UTC)',
},
overlapPolicy: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Policy when an action would overlap a still-running one (defaults to skip): SCHEDULE_OVERLAP_POLICY_SKIP, SCHEDULE_OVERLAP_POLICY_BUFFER_ONE, SCHEDULE_OVERLAP_POLICY_BUFFER_ALL, SCHEDULE_OVERLAP_POLICY_CANCEL_OTHER, SCHEDULE_OVERLAP_POLICY_TERMINATE_OTHER, or SCHEDULE_OVERLAP_POLICY_ALLOW_ALL',
},
notes: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Human-readable notes stored on the schedule',
},
paused: {
type: 'boolean',
required: false,
visibility: 'user-or-llm',
description: 'Create the schedule in a paused state (defaults to active)',
},
},
request: {
url: (params) => temporalScheduleUrl(params.serverUrl, params.namespace, params.scheduleId),
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => {
const cronString = (params.cronExpressions ?? '')
.split(/[\n,]/)
.map((expression) => expression.trim())
.filter(Boolean)
const interval = toDurationString(params.intervalSeconds)
if (cronString.length === 0 && !interval) {
throw new Error('At least one of cronExpressions or intervalSeconds is required')
}
const spec: Record<string, unknown> = {}
if (cronString.length > 0) spec.cronString = cronString
if (interval) spec.interval = [{ interval }]
if (params.timezone?.trim()) spec.timezoneName = params.timezone.trim()
const startWorkflow: Record<string, unknown> = {
workflowId: params.workflowId.trim(),
workflowType: { name: params.workflowType.trim() },
taskQueue: { name: params.taskQueue.trim() },
}
const input = parseJsonArgs(params.input, 'input')
if (input) startWorkflow.input = input
const schedule: Record<string, unknown> = {
spec,
action: { startWorkflow },
}
if (params.overlapPolicy) schedule.policies = { overlapPolicy: params.overlapPolicy }
const state: Record<string, unknown> = {}
if (params.notes?.trim()) state.notes = params.notes.trim()
if (params.paused) state.paused = true
if (Object.keys(state).length > 0) schedule.state = state
return {
schedule,
identity: TEMPORAL_CLIENT_IDENTITY,
requestId: generateId(),
}
},
},
transformResponse: async (response: Response, params) => {
await parseTemporalResponse(response, 'create schedule')
return {
success: true,
output: {
scheduleId: params?.scheduleId ?? '',
},
}
},
outputs: {
scheduleId: { type: 'string', description: 'ID of the created schedule' },
},
}
@@ -0,0 +1,70 @@
import type {
TemporalDeleteScheduleParams,
TemporalScheduleMutationResponse,
} from '@/tools/temporal/types'
import {
parseTemporalResponse,
TEMPORAL_CLIENT_IDENTITY,
temporalRequestHeaders,
temporalScheduleUrl,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const deleteScheduleTool: ToolConfig<
TemporalDeleteScheduleParams,
TemporalScheduleMutationResponse
> = {
id: 'temporal_delete_schedule',
name: 'Temporal Delete Schedule',
description:
'Delete a Temporal schedule. Workflows already started by the schedule keep running.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
scheduleId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the schedule to delete',
},
},
request: {
url: (params) =>
`${temporalScheduleUrl(params.serverUrl, params.namespace, params.scheduleId)}?identity=${encodeURIComponent(TEMPORAL_CLIENT_IDENTITY)}`,
method: 'DELETE',
headers: (params) => temporalRequestHeaders(params),
},
transformResponse: async (response: Response, params) => {
await parseTemporalResponse(response, 'delete schedule')
return {
success: true,
output: {
scheduleId: params?.scheduleId ?? '',
},
}
},
outputs: {
scheduleId: { type: 'string', description: 'ID of the deleted schedule' },
},
}
@@ -0,0 +1,147 @@
import type {
TemporalDescribeScheduleParams,
TemporalDescribeScheduleResponse,
} from '@/tools/temporal/types'
import {
parseTemporalResponse,
temporalRequestHeaders,
temporalScheduleUrl,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
interface RawDescribeScheduleResponse {
schedule?: {
spec?: Record<string, unknown>
action?: {
startWorkflow?: {
workflowId?: string
workflowType?: { name?: string }
taskQueue?: { name?: string }
}
}
state?: {
notes?: string
paused?: boolean
}
}
info?: {
recentActions?: Array<{
scheduleTime?: string
actualTime?: string
startWorkflowResult?: { workflowId?: string; runId?: string }
}>
futureActionTimes?: string[]
}
}
export const describeScheduleTool: ToolConfig<
TemporalDescribeScheduleParams,
TemporalDescribeScheduleResponse
> = {
id: 'temporal_describe_schedule',
name: 'Temporal Describe Schedule',
description:
'Get the configuration and current state of a Temporal schedule, including its spec, recent actions, and upcoming run times.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
scheduleId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the schedule to describe',
},
},
request: {
url: (params) => temporalScheduleUrl(params.serverUrl, params.namespace, params.scheduleId),
method: 'GET',
headers: (params) => temporalRequestHeaders(params),
},
transformResponse: async (response: Response, params) => {
const data = await parseTemporalResponse<RawDescribeScheduleResponse>(
response,
'describe schedule'
)
const startWorkflow = data.schedule?.action?.startWorkflow
return {
success: true,
output: {
scheduleId: params?.scheduleId ?? '',
paused: data.schedule?.state?.paused ?? false,
notes: data.schedule?.state?.notes ?? null,
workflowType: startWorkflow?.workflowType?.name ?? null,
taskQueue: startWorkflow?.taskQueue?.name ?? null,
workflowId: startWorkflow?.workflowId ?? null,
spec: data.schedule?.spec ?? null,
recentActions: (data.info?.recentActions ?? []).map((action) => ({
scheduleTime: action.scheduleTime ?? null,
actualTime: action.actualTime ?? null,
workflowId: action.startWorkflowResult?.workflowId ?? null,
runId: action.startWorkflowResult?.runId ?? null,
})),
futureActionTimes: data.info?.futureActionTimes ?? [],
},
}
},
outputs: {
scheduleId: { type: 'string', description: 'Schedule ID' },
paused: { type: 'boolean', description: 'Whether the schedule is paused' },
notes: { type: 'string', description: 'Human-readable notes on the schedule', optional: true },
workflowType: {
type: 'string',
description: 'Workflow type the schedule starts',
optional: true,
},
taskQueue: {
type: 'string',
description: 'Task queue used for started workflows',
optional: true,
},
workflowId: {
type: 'string',
description: 'Workflow ID template for started workflows',
optional: true,
},
spec: {
type: 'json',
description: 'Schedule spec (calendars, intervals, cron strings, jitter, time zone)',
optional: true,
},
recentActions: {
type: 'array',
description: 'Most recent actions taken by the schedule',
items: {
type: 'object',
properties: {
scheduleTime: { type: 'string', description: 'Nominal scheduled time (RFC 3339)' },
actualTime: { type: 'string', description: 'Actual time the action ran (RFC 3339)' },
workflowId: { type: 'string', description: 'Workflow ID of the started execution' },
runId: { type: 'string', description: 'Run ID of the started execution' },
},
},
},
futureActionTimes: { type: 'json', description: 'Upcoming action times (RFC 3339)' },
},
}
@@ -0,0 +1,107 @@
import type {
TemporalDescribeTaskQueueParams,
TemporalDescribeTaskQueueResponse,
} from '@/tools/temporal/types'
import {
parseTemporalResponse,
temporalNamespaceUrl,
temporalRequestHeaders,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const describeTaskQueueTool: ToolConfig<
TemporalDescribeTaskQueueParams,
TemporalDescribeTaskQueueResponse
> = {
id: 'temporal_describe_task_queue',
name: 'Temporal Describe Task Queue',
description:
'List the workers currently polling a Temporal task queue, to check whether a workflow or activity has live workers.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
taskQueue: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the task queue to describe (e.g., orders)',
},
taskQueueType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Type of pollers to list: TASK_QUEUE_TYPE_WORKFLOW (default) or TASK_QUEUE_TYPE_ACTIVITY',
},
},
request: {
url: (params) => {
const base = `${temporalNamespaceUrl(params.serverUrl, params.namespace)}/task-queues/${encodeURIComponent(params.taskQueue.trim())}`
return params.taskQueueType
? `${base}?taskQueueType=${encodeURIComponent(params.taskQueueType)}`
: base
},
method: 'GET',
headers: (params) => temporalRequestHeaders(params),
},
transformResponse: async (response: Response, params) => {
const data = await parseTemporalResponse<{
pollers?: Array<{
identity?: string
lastAccessTime?: string
ratePerSecond?: number
}>
}>(response, 'describe task queue')
return {
success: true,
output: {
taskQueue: params?.taskQueue?.trim() ?? '',
pollers: (data.pollers ?? []).map((poller) => ({
identity: poller.identity ?? null,
lastAccessTime: poller.lastAccessTime ?? null,
ratePerSecond: poller.ratePerSecond ?? null,
})),
},
}
},
outputs: {
taskQueue: { type: 'string', description: 'Name of the described task queue' },
pollers: {
type: 'array',
description: 'Workers currently polling the task queue (empty when no workers are running)',
items: {
type: 'object',
properties: {
identity: { type: 'string', description: 'Identity of the polling worker' },
lastAccessTime: {
type: 'string',
description: 'Last time the worker polled the queue (RFC 3339)',
},
ratePerSecond: { type: 'number', description: 'Poller rate per second' },
},
},
},
},
}
@@ -0,0 +1,151 @@
import type {
TemporalDescribeWorkflowParams,
TemporalDescribeWorkflowResponse,
} from '@/tools/temporal/types'
import {
decodePayloadMap,
mapExecutionInfo,
parseTemporalResponse,
stripEnumPrefix,
type TemporalPayload,
type TemporalRawExecutionInfo,
temporalRequestHeaders,
temporalWorkflowUrl,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
interface RawDescribeResponse {
workflowExecutionInfo?: TemporalRawExecutionInfo & {
memo?: { fields?: Record<string, TemporalPayload> }
searchAttributes?: { indexedFields?: Record<string, TemporalPayload> }
}
pendingActivities?: Array<{
activityId?: string
activityType?: { name?: string }
state?: string
attempt?: number
lastFailure?: { message?: string }
}>
}
export const describeWorkflowTool: ToolConfig<
TemporalDescribeWorkflowParams,
TemporalDescribeWorkflowResponse
> = {
id: 'temporal_describe_workflow',
name: 'Temporal Describe Workflow',
description:
'Get the current state of a Temporal workflow execution, including status, timing, memo, search attributes, and pending activities.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
workflowId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow ID of the execution to describe',
},
runId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Run ID of a specific run to describe (defaults to the latest run)',
},
},
request: {
url: (params) => {
const base = temporalWorkflowUrl(params.serverUrl, params.namespace, params.workflowId)
const runId = params.runId?.trim()
return runId ? `${base}?execution.runId=${encodeURIComponent(runId)}` : base
},
method: 'GET',
headers: (params) => temporalRequestHeaders(params),
},
transformResponse: async (response: Response) => {
const data = await parseTemporalResponse<RawDescribeResponse>(response, 'describe workflow')
const info = data.workflowExecutionInfo
return {
success: true,
output: {
...mapExecutionInfo(info),
memo: decodePayloadMap(info?.memo?.fields),
searchAttributes: decodePayloadMap(info?.searchAttributes?.indexedFields),
pendingActivities: (data.pendingActivities ?? []).map((activity) => ({
activityId: activity.activityId ?? null,
activityType: activity.activityType?.name ?? null,
state: stripEnumPrefix(activity.state, 'PENDING_ACTIVITY_STATE_'),
attempt: activity.attempt ?? null,
lastFailureMessage: activity.lastFailure?.message ?? null,
})),
},
}
},
outputs: {
workflowId: { type: 'string', description: 'Workflow ID of the execution' },
runId: { type: 'string', description: 'Run ID of the execution' },
workflowType: { type: 'string', description: 'Workflow type name' },
status: {
type: 'string',
description:
'Execution status (RUNNING, COMPLETED, FAILED, CANCELED, TERMINATED, CONTINUED_AS_NEW, or TIMED_OUT)',
},
startTime: { type: 'string', description: 'Start time of the execution (RFC 3339)' },
closeTime: {
type: 'string',
description: 'Close time of the execution (RFC 3339), null while running',
optional: true,
},
executionTime: {
type: 'string',
description: 'Effective execution start time (RFC 3339), e.g. the first cron run time',
optional: true,
},
historyLength: { type: 'number', description: 'Number of events in the workflow history' },
taskQueue: { type: 'string', description: 'Task queue of the execution' },
memo: { type: 'json', description: 'Decoded memo fields attached to the execution' },
searchAttributes: { type: 'json', description: 'Decoded search attribute values' },
pendingActivities: {
type: 'array',
description: 'Activities currently pending on the execution',
items: {
type: 'object',
properties: {
activityId: { type: 'string', description: 'Activity ID' },
activityType: { type: 'string', description: 'Activity type name' },
state: {
type: 'string',
description:
'Pending state (SCHEDULED, STARTED, CANCEL_REQUESTED, PAUSED, or PAUSE_REQUESTED)',
},
attempt: { type: 'number', description: 'Current attempt number' },
lastFailureMessage: {
type: 'string',
description: 'Message of the most recent failure, if the activity is retrying',
},
},
},
},
},
}
@@ -0,0 +1,137 @@
import type {
TemporalGetWorkflowHistoryParams,
TemporalGetWorkflowHistoryResponse,
} from '@/tools/temporal/types'
import {
mapHistoryEvent,
parseTemporalResponse,
type TemporalRawHistoryEvent,
temporalRequestHeaders,
temporalWorkflowUrl,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const getWorkflowHistoryTool: ToolConfig<
TemporalGetWorkflowHistoryParams,
TemporalGetWorkflowHistoryResponse
> = {
id: 'temporal_get_workflow_history',
name: 'Temporal Get Workflow History',
description:
'Fetch the event history of a Temporal workflow execution, optionally filtered to just the close event.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
workflowId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow ID of the execution',
},
runId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Run ID of a specific run (defaults to the latest run)',
},
maximumPageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of history events to return per page',
},
nextPageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Page token from a previous response, for pagination',
},
historyEventFilterType: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Event filter: HISTORY_EVENT_FILTER_TYPE_ALL_EVENT (default) or HISTORY_EVENT_FILTER_TYPE_CLOSE_EVENT to return only the final close event',
},
},
request: {
url: (params) => {
const search = new URLSearchParams()
const runId = params.runId?.trim()
if (runId) search.set('execution.runId', runId)
const pageSize = Number(params.maximumPageSize)
if (Number.isFinite(pageSize) && pageSize > 0) {
search.set('maximumPageSize', String(pageSize))
}
if (params.nextPageToken) search.set('nextPageToken', params.nextPageToken)
if (params.historyEventFilterType) {
search.set('historyEventFilterType', params.historyEventFilterType)
}
const queryString = search.toString()
return `${temporalWorkflowUrl(params.serverUrl, params.namespace, params.workflowId)}/history${queryString ? `?${queryString}` : ''}`
},
method: 'GET',
headers: (params) => temporalRequestHeaders(params),
},
transformResponse: async (response: Response) => {
const data = await parseTemporalResponse<{
history?: { events?: TemporalRawHistoryEvent[] }
nextPageToken?: string
}>(response, 'get workflow history')
return {
success: true,
output: {
events: (data.history?.events ?? []).map(mapHistoryEvent),
nextPageToken: data.nextPageToken || null,
},
}
},
outputs: {
events: {
type: 'array',
description: 'History events of the workflow execution, in order',
items: {
type: 'object',
properties: {
eventId: { type: 'number', description: 'Sequential ID of the event' },
eventTime: { type: 'string', description: 'Time the event was recorded (RFC 3339)' },
eventType: {
type: 'string',
description: 'Event type (e.g., WORKFLOW_EXECUTION_STARTED, ACTIVITY_TASK_COMPLETED)',
},
attributes: {
type: 'json',
description: "The event's type-specific attributes (payload data is base64-encoded)",
},
},
},
},
nextPageToken: {
type: 'string',
description: 'Token for the next page of events, null when no more pages exist',
optional: true,
},
},
}
+41
View File
@@ -0,0 +1,41 @@
import { cancelWorkflowTool } from '@/tools/temporal/cancel_workflow'
import { countWorkflowsTool } from '@/tools/temporal/count_workflows'
import { createScheduleTool } from '@/tools/temporal/create_schedule'
import { deleteScheduleTool } from '@/tools/temporal/delete_schedule'
import { describeScheduleTool } from '@/tools/temporal/describe_schedule'
import { describeTaskQueueTool } from '@/tools/temporal/describe_task_queue'
import { describeWorkflowTool } from '@/tools/temporal/describe_workflow'
import { getWorkflowHistoryTool } from '@/tools/temporal/get_workflow_history'
import { listSchedulesTool } from '@/tools/temporal/list_schedules'
import { listWorkflowsTool } from '@/tools/temporal/list_workflows'
import { pauseScheduleTool } from '@/tools/temporal/pause_schedule'
import { queryWorkflowTool } from '@/tools/temporal/query_workflow'
import { resetWorkflowTool } from '@/tools/temporal/reset_workflow'
import { signalWithStartTool } from '@/tools/temporal/signal_with_start'
import { signalWorkflowTool } from '@/tools/temporal/signal_workflow'
import { startWorkflowTool } from '@/tools/temporal/start_workflow'
import { terminateWorkflowTool } from '@/tools/temporal/terminate_workflow'
import { triggerScheduleTool } from '@/tools/temporal/trigger_schedule'
import { unpauseScheduleTool } from '@/tools/temporal/unpause_schedule'
import { updateWorkflowTool } from '@/tools/temporal/update_workflow'
export const temporalStartWorkflowTool = startWorkflowTool
export const temporalSignalWorkflowTool = signalWorkflowTool
export const temporalSignalWithStartTool = signalWithStartTool
export const temporalQueryWorkflowTool = queryWorkflowTool
export const temporalUpdateWorkflowTool = updateWorkflowTool
export const temporalDescribeWorkflowTool = describeWorkflowTool
export const temporalListWorkflowsTool = listWorkflowsTool
export const temporalCountWorkflowsTool = countWorkflowsTool
export const temporalGetWorkflowHistoryTool = getWorkflowHistoryTool
export const temporalCancelWorkflowTool = cancelWorkflowTool
export const temporalTerminateWorkflowTool = terminateWorkflowTool
export const temporalResetWorkflowTool = resetWorkflowTool
export const temporalDescribeTaskQueueTool = describeTaskQueueTool
export const temporalCreateScheduleTool = createScheduleTool
export const temporalListSchedulesTool = listSchedulesTool
export const temporalDescribeScheduleTool = describeScheduleTool
export const temporalPauseScheduleTool = pauseScheduleTool
export const temporalUnpauseScheduleTool = unpauseScheduleTool
export const temporalTriggerScheduleTool = triggerScheduleTool
export const temporalDeleteScheduleTool = deleteScheduleTool
+135
View File
@@ -0,0 +1,135 @@
import type {
TemporalListSchedulesParams,
TemporalListSchedulesResponse,
} from '@/tools/temporal/types'
import {
parseTemporalResponse,
temporalNamespaceUrl,
temporalRequestHeaders,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
interface RawScheduleListEntry {
scheduleId?: string
info?: {
workflowType?: { name?: string }
notes?: string
paused?: boolean
futureActionTimes?: string[]
}
}
export const listSchedulesTool: ToolConfig<
TemporalListSchedulesParams,
TemporalListSchedulesResponse
> = {
id: 'temporal_list_schedules',
name: 'Temporal List Schedules',
description: 'List schedules in a Temporal namespace.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Visibility filter over schedules, e.g. TemporalSchedulePaused = false (empty lists all schedules)',
},
maximumPageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of schedules to return per page',
},
nextPageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Page token from a previous response, for pagination',
},
},
request: {
url: (params) => {
const search = new URLSearchParams()
if (params.query) search.set('query', params.query)
const pageSize = Number(params.maximumPageSize)
if (Number.isFinite(pageSize) && pageSize > 0) {
search.set('maximumPageSize', String(pageSize))
}
if (params.nextPageToken) search.set('nextPageToken', params.nextPageToken)
const queryString = search.toString()
return `${temporalNamespaceUrl(params.serverUrl, params.namespace)}/schedules${queryString ? `?${queryString}` : ''}`
},
method: 'GET',
headers: (params) => temporalRequestHeaders(params),
},
transformResponse: async (response: Response) => {
const data = await parseTemporalResponse<{
schedules?: RawScheduleListEntry[]
nextPageToken?: string
}>(response, 'list schedules')
return {
success: true,
output: {
schedules: (data.schedules ?? []).map((schedule) => ({
scheduleId: schedule.scheduleId ?? null,
workflowType: schedule.info?.workflowType?.name ?? null,
paused: schedule.info?.paused ?? false,
notes: schedule.info?.notes ?? null,
futureActionTimes: schedule.info?.futureActionTimes ?? [],
})),
nextPageToken: data.nextPageToken || null,
},
}
},
outputs: {
schedules: {
type: 'array',
description: 'Schedules in the namespace',
items: {
type: 'object',
properties: {
scheduleId: { type: 'string', description: 'Schedule ID' },
workflowType: {
type: 'string',
description: 'Workflow type the schedule starts',
},
paused: { type: 'boolean', description: 'Whether the schedule is paused' },
notes: { type: 'string', description: 'Human-readable notes on the schedule' },
futureActionTimes: {
type: 'json',
description: 'Upcoming action times (RFC 3339)',
},
},
},
},
nextPageToken: {
type: 'string',
description: 'Token for the next page of results, null when no more pages exist',
optional: true,
},
},
}
+131
View File
@@ -0,0 +1,131 @@
import type {
TemporalListWorkflowsParams,
TemporalListWorkflowsResponse,
} from '@/tools/temporal/types'
import {
mapExecutionInfo,
parseTemporalResponse,
type TemporalRawExecutionInfo,
temporalNamespaceUrl,
temporalRequestHeaders,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const listWorkflowsTool: ToolConfig<
TemporalListWorkflowsParams,
TemporalListWorkflowsResponse
> = {
id: 'temporal_list_workflows',
name: 'Temporal List Workflows',
description:
'List workflow executions in a Temporal namespace, optionally filtered with a visibility query.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
query: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Visibility list filter, e.g. WorkflowType = "OrderWorkflow" AND ExecutionStatus = "Running" (empty lists all executions)',
},
pageSize: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of executions to return per page',
},
nextPageToken: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Page token from a previous response, for pagination',
},
},
request: {
url: (params) => {
const search = new URLSearchParams()
if (params.query) search.set('query', params.query)
const pageSize = Number(params.pageSize)
if (Number.isFinite(pageSize) && pageSize > 0) search.set('pageSize', String(pageSize))
if (params.nextPageToken) search.set('nextPageToken', params.nextPageToken)
const queryString = search.toString()
return `${temporalNamespaceUrl(params.serverUrl, params.namespace)}/workflows${queryString ? `?${queryString}` : ''}`
},
method: 'GET',
headers: (params) => temporalRequestHeaders(params),
},
transformResponse: async (response: Response) => {
const data = await parseTemporalResponse<{
executions?: TemporalRawExecutionInfo[]
nextPageToken?: string
}>(response, 'list workflows')
return {
success: true,
output: {
executions: (data.executions ?? []).map(mapExecutionInfo),
nextPageToken: data.nextPageToken || null,
},
}
},
outputs: {
executions: {
type: 'array',
description: 'Workflow executions matching the query',
items: {
type: 'object',
properties: {
workflowId: { type: 'string', description: 'Workflow ID of the execution' },
runId: { type: 'string', description: 'Run ID of the execution' },
workflowType: { type: 'string', description: 'Workflow type name' },
status: {
type: 'string',
description:
'Execution status (RUNNING, COMPLETED, FAILED, CANCELED, TERMINATED, CONTINUED_AS_NEW, or TIMED_OUT)',
},
startTime: { type: 'string', description: 'Start time of the execution (RFC 3339)' },
closeTime: {
type: 'string',
description: 'Close time of the execution (RFC 3339), null while running',
},
executionTime: {
type: 'string',
description: 'Effective execution start time (RFC 3339)',
},
historyLength: {
type: 'number',
description: 'Number of events in the workflow history',
},
taskQueue: { type: 'string', description: 'Task queue of the execution' },
},
},
},
nextPageToken: {
type: 'string',
description: 'Token for the next page of results, null when no more pages exist',
optional: true,
},
},
}
+81
View File
@@ -0,0 +1,81 @@
import { generateId } from '@sim/utils/id'
import type {
TemporalPatchScheduleParams,
TemporalScheduleMutationResponse,
} from '@/tools/temporal/types'
import {
parseTemporalResponse,
TEMPORAL_CLIENT_IDENTITY,
temporalRequestHeaders,
temporalScheduleUrl,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const pauseScheduleTool: ToolConfig<
TemporalPatchScheduleParams,
TemporalScheduleMutationResponse
> = {
id: 'temporal_pause_schedule',
name: 'Temporal Pause Schedule',
description: 'Pause a Temporal schedule so it stops taking actions until unpaused.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
scheduleId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the schedule to pause',
},
reason: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: "Reason recorded in the schedule's notes",
},
},
request: {
url: (params) =>
`${temporalScheduleUrl(params.serverUrl, params.namespace, params.scheduleId)}/patch`,
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => ({
patch: { pause: params.reason || 'Paused via Sim' },
identity: TEMPORAL_CLIENT_IDENTITY,
requestId: generateId(),
}),
},
transformResponse: async (response: Response, params) => {
await parseTemporalResponse(response, 'pause schedule')
return {
success: true,
output: {
scheduleId: params?.scheduleId ?? '',
},
}
},
outputs: {
scheduleId: { type: 'string', description: 'ID of the paused schedule' },
},
}
+117
View File
@@ -0,0 +1,117 @@
import type {
TemporalQueryWorkflowParams,
TemporalQueryWorkflowResponse,
} from '@/tools/temporal/types'
import {
decodePayloads,
parseJsonArgs,
parseTemporalResponse,
stripEnumPrefix,
type TemporalPayloads,
temporalRequestHeaders,
temporalWorkflowUrl,
workflowExecutionRef,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const queryWorkflowTool: ToolConfig<
TemporalQueryWorkflowParams,
TemporalQueryWorkflowResponse
> = {
id: 'temporal_query_workflow',
name: 'Temporal Query Workflow',
description:
'Run a synchronous query against the state of a Temporal workflow execution and return the result.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
workflowId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow ID of the execution to query',
},
runId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Run ID of a specific run to query (defaults to the latest run)',
},
queryType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the query handler to invoke (e.g., getStatus)',
},
queryArgs: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Query arguments as JSON. A top-level array is passed as the argument list (one argument per element); any other value is passed as a single argument',
},
},
request: {
url: (params) =>
`${temporalWorkflowUrl(params.serverUrl, params.namespace, params.workflowId)}/query/${encodeURIComponent(params.queryType.trim())}`,
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => {
const query: Record<string, unknown> = { queryType: params.queryType.trim() }
const args = parseJsonArgs(params.queryArgs, 'queryArgs')
if (args) query.queryArgs = args
return { execution: workflowExecutionRef(params.workflowId, params.runId), query }
},
},
transformResponse: async (response: Response, params) => {
const data = await parseTemporalResponse<{
queryResult?: TemporalPayloads
queryRejected?: { status?: string }
}>(response, 'query workflow')
if (data.queryRejected) {
const status = stripEnumPrefix(data.queryRejected.status, 'WORKFLOW_EXECUTION_STATUS_')
throw new Error(`Temporal query workflow rejected: workflow status is ${status ?? 'unknown'}`)
}
const decoded = decodePayloads(data.queryResult)
return {
success: true,
output: {
workflowId: params?.workflowId ?? '',
queryType: params?.queryType ?? '',
result: decoded.length > 1 ? decoded : (decoded[0] ?? null),
},
}
},
outputs: {
workflowId: { type: 'string', description: 'Workflow ID of the queried execution' },
queryType: { type: 'string', description: 'Name of the query that was run' },
result: {
type: 'json',
description:
'Decoded query result. A single payload is returned as its JSON value; multiple payloads are returned as an array',
},
},
}
+107
View File
@@ -0,0 +1,107 @@
import { generateId } from '@sim/utils/id'
import type {
TemporalResetWorkflowParams,
TemporalResetWorkflowResponse,
} from '@/tools/temporal/types'
import {
parseTemporalResponse,
TEMPORAL_CLIENT_IDENTITY,
temporalRequestHeaders,
temporalWorkflowUrl,
workflowExecutionRef,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const resetWorkflowTool: ToolConfig<
TemporalResetWorkflowParams,
TemporalResetWorkflowResponse
> = {
id: 'temporal_reset_workflow',
name: 'Temporal Reset Workflow',
description:
'Reset a Temporal workflow execution to a past workflow task, terminating the current run and replaying from the reset point in a new run.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
workflowId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow ID of the execution to reset',
},
runId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Run ID of a specific run to reset (defaults to the latest run)',
},
workflowTaskFinishEventId: {
type: 'number',
required: true,
visibility: 'user-or-llm',
description:
'Event ID of the workflow task finish event to reset to — a WORKFLOW_TASK_COMPLETED, WORKFLOW_TASK_TIMED_OUT, WORKFLOW_TASK_FAILED, or WORKFLOW_TASK_STARTED event (find it with Get Workflow History)',
},
reason: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Reason for the reset, recorded in the workflow history',
},
},
request: {
url: (params) =>
`${temporalWorkflowUrl(params.serverUrl, params.namespace, params.workflowId)}/reset`,
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => {
const eventId = Number(params.workflowTaskFinishEventId)
if (!Number.isFinite(eventId) || eventId <= 0) {
throw new Error('workflowTaskFinishEventId must be a positive event ID')
}
const body: Record<string, unknown> = {
workflowExecution: workflowExecutionRef(params.workflowId, params.runId),
workflowTaskFinishEventId: eventId,
identity: TEMPORAL_CLIENT_IDENTITY,
requestId: generateId(),
}
if (params.reason) body.reason = params.reason
return body
},
},
transformResponse: async (response: Response, params) => {
const data = await parseTemporalResponse<{ runId?: string }>(response, 'reset workflow')
return {
success: true,
output: {
workflowId: params?.workflowId ?? '',
runId: data.runId ?? '',
},
}
},
outputs: {
workflowId: { type: 'string', description: 'Workflow ID of the reset execution' },
runId: { type: 'string', description: 'Run ID of the new run created by the reset' },
},
}
@@ -0,0 +1,187 @@
import { generateId } from '@sim/utils/id'
import type {
TemporalSignalWithStartParams,
TemporalSignalWithStartResponse,
} from '@/tools/temporal/types'
import {
parseJsonArgs,
parseJsonPayloadMap,
parseTemporalResponse,
TEMPORAL_CLIENT_IDENTITY,
temporalRequestHeaders,
temporalWorkflowUrl,
toDurationString,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const signalWithStartTool: ToolConfig<
TemporalSignalWithStartParams,
TemporalSignalWithStartResponse
> = {
id: 'temporal_signal_with_start',
name: 'Temporal Signal With Start',
description:
'Atomically signal a Temporal workflow, starting it first if it is not already running, so the signal is never lost.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
workflowId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow ID to signal, or to start and signal (e.g., order-1234)',
},
workflowType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Registered workflow type name to start if the workflow is not running',
},
taskQueue: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Task queue the workflow worker polls (e.g., orders)',
},
signalName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the signal handler to invoke (e.g., approve-order)',
},
input: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Workflow start input as JSON, used only when a new execution is started. A top-level array is passed as the argument list; any other value is passed as a single argument',
},
signalInput: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Signal input as JSON. A top-level array is passed as the argument list (one argument per element); any other value is passed as a single argument',
},
workflowIdReusePolicy: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Policy for reusing a closed workflow ID: WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE, WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY, WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE, or WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING',
},
workflowIdConflictPolicy: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Policy when a workflow with the same ID is already running (defaults to using the existing run): WORKFLOW_ID_CONFLICT_POLICY_FAIL, WORKFLOW_ID_CONFLICT_POLICY_USE_EXISTING, or WORKFLOW_ID_CONFLICT_POLICY_TERMINATE_EXISTING',
},
cronSchedule: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cron schedule for recurring executions (e.g., "0 12 * * *")',
},
executionTimeoutSeconds: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description:
'Total workflow execution timeout in seconds, including retries and continue-as-new',
},
runTimeoutSeconds: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Timeout for a single workflow run in seconds',
},
memo: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON object of memo fields to attach to the execution',
},
searchAttributes: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON object of search attribute values to index the execution with',
},
},
request: {
url: (params) =>
`${temporalWorkflowUrl(params.serverUrl, params.namespace, params.workflowId)}/signal-with-start/${encodeURIComponent(params.signalName.trim())}`,
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => {
const body: Record<string, unknown> = {
workflowType: { name: params.workflowType.trim() },
taskQueue: { name: params.taskQueue.trim() },
identity: TEMPORAL_CLIENT_IDENTITY,
requestId: generateId(),
}
const input = parseJsonArgs(params.input, 'input')
if (input) body.input = input
const signalInput = parseJsonArgs(params.signalInput, 'signalInput')
if (signalInput) body.signalInput = signalInput
if (params.workflowIdReusePolicy) body.workflowIdReusePolicy = params.workflowIdReusePolicy
if (params.workflowIdConflictPolicy) {
body.workflowIdConflictPolicy = params.workflowIdConflictPolicy
}
if (params.cronSchedule) body.cronSchedule = params.cronSchedule
const executionTimeout = toDurationString(params.executionTimeoutSeconds)
if (executionTimeout) body.workflowExecutionTimeout = executionTimeout
const runTimeout = toDurationString(params.runTimeoutSeconds)
if (runTimeout) body.workflowRunTimeout = runTimeout
const memoFields = parseJsonPayloadMap(params.memo, 'memo')
if (memoFields) body.memo = { fields: memoFields }
const searchAttributeFields = parseJsonPayloadMap(params.searchAttributes, 'searchAttributes')
if (searchAttributeFields) body.searchAttributes = { indexedFields: searchAttributeFields }
return body
},
},
transformResponse: async (response: Response, params) => {
const data = await parseTemporalResponse<{ runId?: string; started?: boolean }>(
response,
'signal with start'
)
return {
success: true,
output: {
workflowId: params?.workflowId ?? '',
runId: data.runId ?? '',
started: data.started ?? false,
},
}
},
outputs: {
workflowId: { type: 'string', description: 'Workflow ID of the signaled execution' },
runId: { type: 'string', description: 'Run ID of the signaled (or newly started) execution' },
started: {
type: 'boolean',
description: 'Whether this call started a new execution (false when only signaled)',
},
},
}
+103
View File
@@ -0,0 +1,103 @@
import { generateId } from '@sim/utils/id'
import type {
TemporalSignalWorkflowParams,
TemporalSignalWorkflowResponse,
} from '@/tools/temporal/types'
import {
parseJsonArgs,
parseTemporalResponse,
TEMPORAL_CLIENT_IDENTITY,
temporalRequestHeaders,
temporalWorkflowUrl,
workflowExecutionRef,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const signalWorkflowTool: ToolConfig<
TemporalSignalWorkflowParams,
TemporalSignalWorkflowResponse
> = {
id: 'temporal_signal_workflow',
name: 'Temporal Signal Workflow',
description: 'Send a signal to a running Temporal workflow execution.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
workflowId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow ID of the execution to signal',
},
runId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Run ID of a specific run to signal (defaults to the latest run)',
},
signalName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the signal handler to invoke (e.g., approve-order)',
},
signalInput: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Signal input as JSON. A top-level array is passed as the argument list (one argument per element); any other value is passed as a single argument',
},
},
request: {
url: (params) =>
`${temporalWorkflowUrl(params.serverUrl, params.namespace, params.workflowId)}/signal/${encodeURIComponent(params.signalName.trim())}`,
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => {
const body: Record<string, unknown> = {
workflowExecution: workflowExecutionRef(params.workflowId, params.runId),
identity: TEMPORAL_CLIENT_IDENTITY,
requestId: generateId(),
}
const input = parseJsonArgs(params.signalInput, 'signalInput')
if (input) body.input = input
return body
},
},
transformResponse: async (response: Response, params) => {
await parseTemporalResponse(response, 'signal workflow')
return {
success: true,
output: {
workflowId: params?.workflowId ?? '',
signalName: params?.signalName ?? '',
},
}
},
outputs: {
workflowId: { type: 'string', description: 'Workflow ID of the signaled execution' },
signalName: { type: 'string', description: 'Name of the signal that was sent' },
},
}
+171
View File
@@ -0,0 +1,171 @@
import { generateId } from '@sim/utils/id'
import type {
TemporalStartWorkflowParams,
TemporalStartWorkflowResponse,
} from '@/tools/temporal/types'
import {
parseJsonArgs,
parseJsonPayloadMap,
parseTemporalResponse,
TEMPORAL_CLIENT_IDENTITY,
temporalRequestHeaders,
temporalWorkflowUrl,
toDurationString,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const startWorkflowTool: ToolConfig<
TemporalStartWorkflowParams,
TemporalStartWorkflowResponse
> = {
id: 'temporal_start_workflow',
name: 'Temporal Start Workflow',
description: 'Start a new workflow execution on a Temporal cluster.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
workflowId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Unique workflow ID for the new execution (e.g., order-1234)',
},
workflowType: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Registered workflow type name to run (e.g., OrderWorkflow)',
},
taskQueue: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Task queue the workflow worker polls (e.g., orders)',
},
input: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Workflow input as JSON. A top-level array is passed as the argument list (one argument per element); any other value is passed as a single argument',
},
workflowIdReusePolicy: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Policy for reusing a closed workflow ID: WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE, WORKFLOW_ID_REUSE_POLICY_ALLOW_DUPLICATE_FAILED_ONLY, WORKFLOW_ID_REUSE_POLICY_REJECT_DUPLICATE, or WORKFLOW_ID_REUSE_POLICY_TERMINATE_IF_RUNNING',
},
workflowIdConflictPolicy: {
type: 'string',
required: false,
visibility: 'user-only',
description:
'Policy when a workflow with the same ID is already running: WORKFLOW_ID_CONFLICT_POLICY_FAIL, WORKFLOW_ID_CONFLICT_POLICY_USE_EXISTING, or WORKFLOW_ID_CONFLICT_POLICY_TERMINATE_EXISTING',
},
cronSchedule: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cron schedule for recurring executions (e.g., "0 12 * * *")',
},
executionTimeoutSeconds: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description:
'Total workflow execution timeout in seconds, including retries and continue-as-new',
},
runTimeoutSeconds: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Timeout for a single workflow run in seconds',
},
memo: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON object of memo fields to attach to the execution',
},
searchAttributes: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'JSON object of search attribute values to index the execution with',
},
},
request: {
url: (params) => temporalWorkflowUrl(params.serverUrl, params.namespace, params.workflowId),
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => {
const body: Record<string, unknown> = {
workflowType: { name: params.workflowType.trim() },
taskQueue: { name: params.taskQueue.trim() },
identity: TEMPORAL_CLIENT_IDENTITY,
requestId: generateId(),
}
const input = parseJsonArgs(params.input, 'input')
if (input) body.input = input
if (params.workflowIdReusePolicy) body.workflowIdReusePolicy = params.workflowIdReusePolicy
if (params.workflowIdConflictPolicy) {
body.workflowIdConflictPolicy = params.workflowIdConflictPolicy
}
if (params.cronSchedule) body.cronSchedule = params.cronSchedule
const executionTimeout = toDurationString(params.executionTimeoutSeconds)
if (executionTimeout) body.workflowExecutionTimeout = executionTimeout
const runTimeout = toDurationString(params.runTimeoutSeconds)
if (runTimeout) body.workflowRunTimeout = runTimeout
const memoFields = parseJsonPayloadMap(params.memo, 'memo')
if (memoFields) body.memo = { fields: memoFields }
const searchAttributeFields = parseJsonPayloadMap(params.searchAttributes, 'searchAttributes')
if (searchAttributeFields) body.searchAttributes = { indexedFields: searchAttributeFields }
return body
},
},
transformResponse: async (response: Response, params) => {
const data = await parseTemporalResponse<{ runId?: string; started?: boolean }>(
response,
'start workflow'
)
return {
success: true,
output: {
workflowId: params?.workflowId ?? '',
runId: data.runId ?? '',
started: data.started ?? false,
},
}
},
outputs: {
workflowId: { type: 'string', description: 'Workflow ID of the execution' },
runId: { type: 'string', description: 'Run ID of the started workflow execution' },
started: {
type: 'boolean',
description:
'Whether a new execution was started (false when an existing execution was reused)',
},
},
}
@@ -0,0 +1,91 @@
import type {
TemporalTerminateWorkflowParams,
TemporalTerminateWorkflowResponse,
} from '@/tools/temporal/types'
import {
parseTemporalResponse,
TEMPORAL_CLIENT_IDENTITY,
temporalRequestHeaders,
temporalWorkflowUrl,
workflowExecutionRef,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const terminateWorkflowTool: ToolConfig<
TemporalTerminateWorkflowParams,
TemporalTerminateWorkflowResponse
> = {
id: 'temporal_terminate_workflow',
name: 'Temporal Terminate Workflow',
description:
'Forcefully terminate a Temporal workflow execution immediately, without giving the workflow a chance to react.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
workflowId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow ID of the execution to terminate',
},
runId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Run ID of a specific run to terminate (defaults to the latest run)',
},
reason: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Reason for the termination, recorded in the workflow history',
},
},
request: {
url: (params) =>
`${temporalWorkflowUrl(params.serverUrl, params.namespace, params.workflowId)}/terminate`,
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => {
const body: Record<string, unknown> = {
workflowExecution: workflowExecutionRef(params.workflowId, params.runId),
identity: TEMPORAL_CLIENT_IDENTITY,
}
if (params.reason) body.reason = params.reason
return body
},
},
transformResponse: async (response: Response, params) => {
await parseTemporalResponse(response, 'terminate workflow')
return {
success: true,
output: {
workflowId: params?.workflowId ?? '',
},
}
},
outputs: {
workflowId: { type: 'string', description: 'Workflow ID of the terminated execution' },
},
}
@@ -0,0 +1,86 @@
import { generateId } from '@sim/utils/id'
import type {
TemporalScheduleMutationResponse,
TemporalTriggerScheduleParams,
} from '@/tools/temporal/types'
import {
parseTemporalResponse,
TEMPORAL_CLIENT_IDENTITY,
temporalRequestHeaders,
temporalScheduleUrl,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const triggerScheduleTool: ToolConfig<
TemporalTriggerScheduleParams,
TemporalScheduleMutationResponse
> = {
id: 'temporal_trigger_schedule',
name: 'Temporal Trigger Schedule',
description: 'Trigger an immediate action of a Temporal schedule, outside its normal spec.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
scheduleId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the schedule to trigger',
},
overlapPolicy: {
type: 'string',
required: false,
visibility: 'user-only',
description:
"Overlap policy for the triggered action (defaults to the schedule's policy): SCHEDULE_OVERLAP_POLICY_SKIP, SCHEDULE_OVERLAP_POLICY_BUFFER_ONE, SCHEDULE_OVERLAP_POLICY_BUFFER_ALL, SCHEDULE_OVERLAP_POLICY_CANCEL_OTHER, SCHEDULE_OVERLAP_POLICY_TERMINATE_OTHER, or SCHEDULE_OVERLAP_POLICY_ALLOW_ALL",
},
},
request: {
url: (params) =>
`${temporalScheduleUrl(params.serverUrl, params.namespace, params.scheduleId)}/patch`,
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => {
const triggerImmediately: Record<string, string> = {}
if (params.overlapPolicy) triggerImmediately.overlapPolicy = params.overlapPolicy
return {
patch: { triggerImmediately },
identity: TEMPORAL_CLIENT_IDENTITY,
requestId: generateId(),
}
},
},
transformResponse: async (response: Response, params) => {
await parseTemporalResponse(response, 'trigger schedule')
return {
success: true,
output: {
scheduleId: params?.scheduleId ?? '',
},
}
},
outputs: {
scheduleId: { type: 'string', description: 'ID of the triggered schedule' },
},
}
+326
View File
@@ -0,0 +1,326 @@
import type { ToolResponse } from '@/tools/types'
export interface TemporalBaseParams {
serverUrl: string
namespace: string
apiKey?: string
}
export interface TemporalExecutionSummary {
workflowId: string | null
runId: string | null
workflowType: string | null
status: string | null
startTime: string | null
closeTime: string | null
executionTime: string | null
historyLength: number | null
taskQueue: string | null
}
export interface TemporalPendingActivity {
activityId: string | null
activityType: string | null
state: string | null
attempt: number | null
lastFailureMessage: string | null
}
export interface TemporalHistoryEventSummary {
eventId: number | null
eventTime: string | null
eventType: string | null
attributes: Record<string, unknown> | null
}
export interface TemporalStartWorkflowParams extends TemporalBaseParams {
workflowId: string
workflowType: string
taskQueue: string
input?: string
workflowIdReusePolicy?: string
workflowIdConflictPolicy?: string
cronSchedule?: string
executionTimeoutSeconds?: number
runTimeoutSeconds?: number
memo?: string
searchAttributes?: string
}
export interface TemporalStartWorkflowResponse extends ToolResponse {
output: {
workflowId: string
runId: string
started: boolean
}
}
export interface TemporalSignalWorkflowParams extends TemporalBaseParams {
workflowId: string
runId?: string
signalName: string
signalInput?: string
}
export interface TemporalSignalWorkflowResponse extends ToolResponse {
output: {
workflowId: string
signalName: string
}
}
export interface TemporalSignalWithStartParams extends TemporalBaseParams {
workflowId: string
workflowType: string
taskQueue: string
signalName: string
input?: string
signalInput?: string
workflowIdReusePolicy?: string
workflowIdConflictPolicy?: string
cronSchedule?: string
executionTimeoutSeconds?: number
runTimeoutSeconds?: number
memo?: string
searchAttributes?: string
}
export interface TemporalSignalWithStartResponse extends ToolResponse {
output: {
workflowId: string
runId: string
started: boolean
}
}
export interface TemporalQueryWorkflowParams extends TemporalBaseParams {
workflowId: string
runId?: string
queryType: string
queryArgs?: string
}
export interface TemporalQueryWorkflowResponse extends ToolResponse {
output: {
workflowId: string
queryType: string
result: unknown
}
}
export interface TemporalDescribeWorkflowParams extends TemporalBaseParams {
workflowId: string
runId?: string
}
export interface TemporalDescribeWorkflowResponse extends ToolResponse {
output: TemporalExecutionSummary & {
memo: Record<string, unknown> | null
searchAttributes: Record<string, unknown> | null
pendingActivities: TemporalPendingActivity[]
}
}
export interface TemporalListWorkflowsParams extends TemporalBaseParams {
query?: string
pageSize?: number
nextPageToken?: string
}
export interface TemporalListWorkflowsResponse extends ToolResponse {
output: {
executions: TemporalExecutionSummary[]
nextPageToken: string | null
}
}
export interface TemporalGetWorkflowHistoryParams extends TemporalBaseParams {
workflowId: string
runId?: string
maximumPageSize?: number
nextPageToken?: string
historyEventFilterType?: string
}
export interface TemporalGetWorkflowHistoryResponse extends ToolResponse {
output: {
events: TemporalHistoryEventSummary[]
nextPageToken: string | null
}
}
export interface TemporalUpdateWorkflowParams extends TemporalBaseParams {
workflowId: string
runId?: string
updateName: string
updateArgs?: string
}
export interface TemporalUpdateWorkflowResponse extends ToolResponse {
output: {
workflowId: string
updateName: string
result: unknown
}
}
export interface TemporalCountWorkflowsParams extends TemporalBaseParams {
query?: string
}
export interface TemporalCountWorkflowsResponse extends ToolResponse {
output: {
count: number
groups: Array<{ values: unknown[]; count: number }>
}
}
export interface TemporalResetWorkflowParams extends TemporalBaseParams {
workflowId: string
runId?: string
workflowTaskFinishEventId: number
reason?: string
}
export interface TemporalResetWorkflowResponse extends ToolResponse {
output: {
workflowId: string
runId: string
}
}
export interface TemporalScheduleSummary {
scheduleId: string | null
workflowType: string | null
paused: boolean
notes: string | null
futureActionTimes: string[]
}
export interface TemporalListSchedulesParams extends TemporalBaseParams {
query?: string
maximumPageSize?: number
nextPageToken?: string
}
export interface TemporalListSchedulesResponse extends ToolResponse {
output: {
schedules: TemporalScheduleSummary[]
nextPageToken: string | null
}
}
export interface TemporalDescribeScheduleParams extends TemporalBaseParams {
scheduleId: string
}
export interface TemporalDescribeScheduleResponse extends ToolResponse {
output: {
scheduleId: string
paused: boolean
notes: string | null
workflowType: string | null
taskQueue: string | null
workflowId: string | null
spec: Record<string, unknown> | null
recentActions: Array<{
scheduleTime: string | null
actualTime: string | null
workflowId: string | null
runId: string | null
}>
futureActionTimes: string[]
}
}
export interface TemporalPatchScheduleParams extends TemporalBaseParams {
scheduleId: string
reason?: string
}
export interface TemporalTriggerScheduleParams extends TemporalBaseParams {
scheduleId: string
overlapPolicy?: string
}
export interface TemporalScheduleMutationResponse extends ToolResponse {
output: {
scheduleId: string
}
}
export interface TemporalCreateScheduleParams extends TemporalBaseParams {
scheduleId: string
workflowId: string
workflowType: string
taskQueue: string
input?: string
cronExpressions?: string
intervalSeconds?: number
timezone?: string
overlapPolicy?: string
notes?: string
paused?: boolean
}
export interface TemporalDeleteScheduleParams extends TemporalBaseParams {
scheduleId: string
}
export interface TemporalDescribeTaskQueueParams extends TemporalBaseParams {
taskQueue: string
taskQueueType?: string
}
export interface TemporalDescribeTaskQueueResponse extends ToolResponse {
output: {
taskQueue: string
pollers: Array<{
identity: string | null
lastAccessTime: string | null
ratePerSecond: number | null
}>
}
}
export interface TemporalCancelWorkflowParams extends TemporalBaseParams {
workflowId: string
runId?: string
reason?: string
}
export interface TemporalCancelWorkflowResponse extends ToolResponse {
output: {
workflowId: string
}
}
export interface TemporalTerminateWorkflowParams extends TemporalBaseParams {
workflowId: string
runId?: string
reason?: string
}
export interface TemporalTerminateWorkflowResponse extends ToolResponse {
output: {
workflowId: string
}
}
export type TemporalResponse =
| TemporalStartWorkflowResponse
| TemporalSignalWorkflowResponse
| TemporalSignalWithStartResponse
| TemporalQueryWorkflowResponse
| TemporalUpdateWorkflowResponse
| TemporalDescribeWorkflowResponse
| TemporalListWorkflowsResponse
| TemporalCountWorkflowsResponse
| TemporalGetWorkflowHistoryResponse
| TemporalCancelWorkflowResponse
| TemporalTerminateWorkflowResponse
| TemporalResetWorkflowResponse
| TemporalListSchedulesResponse
| TemporalDescribeScheduleResponse
| TemporalScheduleMutationResponse
| TemporalDescribeTaskQueueResponse
@@ -0,0 +1,81 @@
import { generateId } from '@sim/utils/id'
import type {
TemporalPatchScheduleParams,
TemporalScheduleMutationResponse,
} from '@/tools/temporal/types'
import {
parseTemporalResponse,
TEMPORAL_CLIENT_IDENTITY,
temporalRequestHeaders,
temporalScheduleUrl,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const unpauseScheduleTool: ToolConfig<
TemporalPatchScheduleParams,
TemporalScheduleMutationResponse
> = {
id: 'temporal_unpause_schedule',
name: 'Temporal Unpause Schedule',
description: 'Unpause a Temporal schedule so it resumes taking actions.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
scheduleId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'ID of the schedule to unpause',
},
reason: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: "Reason recorded in the schedule's notes",
},
},
request: {
url: (params) =>
`${temporalScheduleUrl(params.serverUrl, params.namespace, params.scheduleId)}/patch`,
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => ({
patch: { unpause: params.reason || 'Unpaused via Sim' },
identity: TEMPORAL_CLIENT_IDENTITY,
requestId: generateId(),
}),
},
transformResponse: async (response: Response, params) => {
await parseTemporalResponse(response, 'unpause schedule')
return {
success: true,
output: {
scheduleId: params?.scheduleId ?? '',
},
}
},
outputs: {
scheduleId: { type: 'string', description: 'ID of the unpaused schedule' },
},
}
+137
View File
@@ -0,0 +1,137 @@
import { generateId } from '@sim/utils/id'
import type {
TemporalUpdateWorkflowParams,
TemporalUpdateWorkflowResponse,
} from '@/tools/temporal/types'
import {
decodePayloads,
parseJsonArgs,
parseTemporalResponse,
stripEnumPrefix,
TEMPORAL_CLIENT_IDENTITY,
type TemporalPayloads,
temporalRequestHeaders,
temporalWorkflowUrl,
workflowExecutionRef,
} from '@/tools/temporal/utils'
import type { ToolConfig } from '@/tools/types'
export const updateWorkflowTool: ToolConfig<
TemporalUpdateWorkflowParams,
TemporalUpdateWorkflowResponse
> = {
id: 'temporal_update_workflow',
name: 'Temporal Update Workflow',
description:
'Invoke an update handler on a running Temporal workflow and wait for its result. Unlike a signal, an update is validated by the workflow and returns a response.',
version: '1.0.0',
params: {
serverUrl: {
type: 'string',
required: true,
visibility: 'user-only',
description: "Base URL of the Temporal server's HTTP API (e.g., http://localhost:7243)",
},
namespace: {
type: 'string',
required: true,
visibility: 'user-only',
description: 'Temporal namespace (e.g., default)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'API key sent as a Bearer token (leave blank for servers without auth)',
},
workflowId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Workflow ID of the execution to update',
},
runId: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Run ID of a specific run to update (defaults to the latest run)',
},
updateName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the update handler to invoke (e.g., addItem)',
},
updateArgs: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Update arguments as JSON. A top-level array is passed as the argument list (one argument per element); any other value is passed as a single argument',
},
},
request: {
url: (params) =>
`${temporalWorkflowUrl(params.serverUrl, params.namespace, params.workflowId)}/update/${encodeURIComponent(params.updateName.trim())}`,
method: 'POST',
headers: (params) => temporalRequestHeaders(params),
body: (params) => {
const input: Record<string, unknown> = { name: params.updateName.trim() }
const args = parseJsonArgs(params.updateArgs, 'updateArgs')
if (args) input.args = args
return {
workflowExecution: workflowExecutionRef(params.workflowId, params.runId),
waitPolicy: { lifecycleStage: 'UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_COMPLETED' },
request: {
meta: { updateId: generateId(), identity: TEMPORAL_CLIENT_IDENTITY },
input,
},
}
},
},
transformResponse: async (response: Response, params) => {
const data = await parseTemporalResponse<{
stage?: string
outcome?: {
success?: TemporalPayloads
failure?: { message?: string }
}
}>(response, 'update workflow')
if (data.outcome?.failure) {
throw new Error(
`Temporal update workflow failed: ${data.outcome.failure.message ?? 'update handler returned a failure'}`
)
}
if (!data.outcome) {
const stage = stripEnumPrefix(data.stage, 'UPDATE_WORKFLOW_EXECUTION_LIFECYCLE_STAGE_')
throw new Error(
`Temporal update workflow did not complete before the request timed out (stage: ${stage ?? 'unknown'}). The update is still being processed by the workflow.`
)
}
const decoded = decodePayloads(data.outcome?.success)
return {
success: true,
output: {
workflowId: params?.workflowId ?? '',
updateName: params?.updateName ?? '',
result: decoded.length > 1 ? decoded : (decoded[0] ?? null),
},
}
},
outputs: {
workflowId: { type: 'string', description: 'Workflow ID of the updated execution' },
updateName: { type: 'string', description: 'Name of the update that was invoked' },
result: {
type: 'json',
description:
'Decoded update result. A single payload is returned as its JSON value; multiple payloads are returned as an array',
},
},
}
+277
View File
@@ -0,0 +1,277 @@
import { truncate } from '@sim/utils/string'
/**
* Identity reported to the Temporal server on write operations so they are
* attributable in workflow histories.
*/
export const TEMPORAL_CLIENT_IDENTITY = 'sim'
const JSON_PLAIN_ENCODING = Buffer.from('json/plain').toString('base64')
/** A Temporal `common.v1.Payload` as serialized by the HTTP API (base64 fields). */
export interface TemporalPayload {
metadata?: Record<string, string>
data?: string
}
/** A Temporal `common.v1.Payloads` collection. */
export interface TemporalPayloads {
payloads?: TemporalPayload[]
}
/** Raw `common.v1.WorkflowExecution` shape returned by the HTTP API. */
export interface TemporalRawExecution {
workflowId?: string
runId?: string
}
/** Raw `workflow.v1.WorkflowExecutionInfo` shape returned by describe/list responses. */
export interface TemporalRawExecutionInfo {
execution?: TemporalRawExecution
type?: { name?: string }
status?: string
startTime?: string
closeTime?: string
executionTime?: string
historyLength?: string
taskQueue?: string
}
/** Raw `history.v1.HistoryEvent` shape returned by the workflow history endpoint. */
export interface TemporalRawHistoryEvent {
eventId?: string
eventTime?: string
eventType?: string
[key: string]: unknown
}
/**
* Builds the `/api/v1/namespaces/{namespace}` base URL for a Temporal server's HTTP API,
* tolerating surrounding whitespace and trailing slashes on the server URL
* (e.g. `http://localhost:7243/` → `http://localhost:7243/api/v1/namespaces/default`).
*/
export function temporalNamespaceUrl(serverUrl: string, namespace: string): string {
const base = serverUrl.trim().replace(/\/+$/, '')
return `${base}/api/v1/namespaces/${encodeURIComponent(namespace.trim())}`
}
/**
* Builds the `/workflows/{workflowId}` URL for a workflow execution, trimming and
* URL-encoding the workflow ID.
*/
export function temporalWorkflowUrl(
serverUrl: string,
namespace: string,
workflowId: string
): string {
return `${temporalNamespaceUrl(serverUrl, namespace)}/workflows/${encodeURIComponent(workflowId.trim())}`
}
/**
* Builds the `/schedules/{scheduleId}` URL for a schedule, trimming and URL-encoding
* the schedule ID.
*/
export function temporalScheduleUrl(
serverUrl: string,
namespace: string,
scheduleId: string
): string {
return `${temporalNamespaceUrl(serverUrl, namespace)}/schedules/${encodeURIComponent(scheduleId.trim())}`
}
/**
* Builds a `common.v1.WorkflowExecution` reference with trimmed IDs, omitting the run ID
* when not provided so the server targets the latest run.
*/
export function workflowExecutionRef(workflowId: string, runId?: string): Record<string, string> {
const ref: Record<string, string> = { workflowId: workflowId.trim() }
if (runId?.trim()) ref.runId = runId.trim()
return ref
}
/**
* Builds the request headers for a Temporal HTTP API call, attaching the API key as a
* Bearer token when one is provided (omitted for servers without authentication).
*
* The Accept header opts out of the server's payload "shorthand" JSON form so responses
* always carry full `{metadata, data}` payload objects that {@link decodePayload} understands.
*/
export function temporalRequestHeaders(params: { apiKey?: string }): Record<string, string> {
const headers: Record<string, string> = {
'Content-Type': 'application/json',
Accept: 'application/json+no-payload-shorthand',
}
if (params.apiKey) headers.Authorization = `Bearer ${params.apiKey.trim()}`
return headers
}
/** Encodes a single JSON value as a Temporal `json/plain` payload. */
export function encodePayload(value: unknown): TemporalPayload {
return {
metadata: { encoding: JSON_PLAIN_ENCODING },
data: Buffer.from(JSON.stringify(value)).toString('base64'),
}
}
/**
* Normalizes a JSON field value: strings are parsed as JSON, already-resolved objects
* and arrays are used as-is, and empty input returns undefined so the field is omitted.
*/
function parseJsonValue(value: unknown, fieldName: string): unknown {
if (value == null) return undefined
if (typeof value === 'string') {
if (!value.trim()) return undefined
try {
return JSON.parse(value)
} catch {
throw new Error(`Invalid JSON in ${fieldName}`)
}
}
return value
}
/**
* Parses a JSON value into Temporal `Payloads`. A top-level array is treated as the
* argument list (one payload per element); any other value becomes a single argument.
* Returns undefined for empty input so optional payload fields can be omitted entirely.
*/
export function parseJsonArgs(value: unknown, fieldName: string): TemporalPayloads | undefined {
const parsed = parseJsonValue(value, fieldName)
if (parsed === undefined) return undefined
const args = Array.isArray(parsed) ? parsed : [parsed]
return { payloads: args.map(encodePayload) }
}
/**
* Parses a JSON object value into a `map<string, Payload>` (memo fields or search
* attribute indexed fields). Returns undefined for empty input.
*/
export function parseJsonPayloadMap(
value: unknown,
fieldName: string
): Record<string, TemporalPayload> | undefined {
const parsed = parseJsonValue(value, fieldName)
if (parsed === undefined) return undefined
if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error(`${fieldName} must be a JSON object`)
}
return Object.fromEntries(
Object.entries(parsed as Record<string, unknown>).map(([key, value]) => [
key,
encodePayload(value),
])
)
}
/**
* Decodes a single Temporal payload: `json/plain` and `json/protobuf` payloads are parsed
* to their JSON value, `binary/null` becomes null, and unknown encodings are returned as
* the original base64 data string.
*/
export function decodePayload(payload: TemporalPayload | undefined): unknown {
if (!payload) return null
const encoding = payload.metadata?.encoding
? Buffer.from(payload.metadata.encoding, 'base64').toString('utf8')
: undefined
if (encoding === 'binary/null') return null
if (payload.data == null) return null
if (encoding === 'json/plain' || encoding === 'json/protobuf') {
const raw = Buffer.from(payload.data, 'base64').toString('utf8')
try {
return JSON.parse(raw)
} catch {
return raw
}
}
return payload.data
}
/** Decodes a Temporal `Payloads` collection into an array of JSON values. */
export function decodePayloads(payloads: TemporalPayloads | undefined): unknown[] {
return (payloads?.payloads ?? []).map(decodePayload)
}
/** Decodes a `map<string, Payload>` (memo / search attributes) into a plain object. */
export function decodePayloadMap(
fields: Record<string, TemporalPayload> | undefined
): Record<string, unknown> | null {
if (!fields) return null
return Object.fromEntries(
Object.entries(fields).map(([key, value]) => [key, decodePayload(value)])
)
}
/** Strips a protobuf enum prefix (e.g. `WORKFLOW_EXECUTION_STATUS_RUNNING` → `RUNNING`). */
export function stripEnumPrefix(value: string | undefined, prefix: string): string | null {
if (!value) return null
return value.startsWith(prefix) ? value.slice(prefix.length) : value
}
/**
* Formats a seconds count as a protobuf JSON duration string (e.g. 3600 → `"3600s"`).
* Returns undefined for missing, non-numeric, or non-positive values so the field is omitted.
*/
export function toDurationString(seconds: number | string | undefined): string | undefined {
if (seconds == null || seconds === '') return undefined
const parsed = Number(seconds)
if (!Number.isFinite(parsed) || parsed <= 0) return undefined
return `${parsed}s`
}
/**
* Maps a raw `WorkflowExecutionInfo` to the flat execution summary shared by the
* describe and list tools. int64 fields arrive as JSON strings and are coerced to numbers.
*/
export function mapExecutionInfo(info: TemporalRawExecutionInfo | undefined) {
return {
workflowId: info?.execution?.workflowId ?? null,
runId: info?.execution?.runId ?? null,
workflowType: info?.type?.name ?? null,
status: stripEnumPrefix(info?.status, 'WORKFLOW_EXECUTION_STATUS_'),
startTime: info?.startTime ?? null,
closeTime: info?.closeTime ?? null,
executionTime: info?.executionTime ?? null,
historyLength: info?.historyLength != null ? Number(info.historyLength) : null,
taskQueue: info?.taskQueue ?? null,
}
}
/**
* Maps a raw history event to a flat shape, extracting the event's `*EventAttributes`
* object (each event carries exactly one, keyed by its type).
*/
export function mapHistoryEvent(event: TemporalRawHistoryEvent) {
const attributesKey = Object.keys(event).find((key) => key.endsWith('EventAttributes'))
return {
eventId: event.eventId != null ? Number(event.eventId) : null,
eventTime: event.eventTime ?? null,
eventType: stripEnumPrefix(event.eventType, 'EVENT_TYPE_'),
attributes: attributesKey ? ((event[attributesKey] as Record<string, unknown>) ?? null) : null,
}
}
/**
* Parses a Temporal HTTP API response body and throws a descriptive error for non-2xx
* replies. grpc-gateway errors carry a top-level `message` field; empty bodies (returned
* by signal/cancel/terminate) parse to an empty object.
*/
export async function parseTemporalResponse<T extends object>(
response: Response,
operation: string
): Promise<T> {
const text = await response.text()
let data: Record<string, unknown> = {}
if (text) {
try {
data = JSON.parse(text) as Record<string, unknown>
} catch {
data = { message: truncate(text, 300) }
}
}
if (!response.ok) {
const message =
typeof data.message === 'string' && data.message ? data.message : `HTTP ${response.status}`
throw new Error(`Temporal ${operation} failed: ${message}`)
}
return data as T
}