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
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:
@@ -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' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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)',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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' },
|
||||
},
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user