chore: import upstream snapshot with attribution
CI / Migrate Dev DB (push) Has been skipped
CI / Detect Version (push) Has been cancelled
CI / Migrate DB (push) Has been cancelled
CI / Build Dev ECR (./docker/app.Dockerfile, ECR_APP) (push) Has been cancelled
CI / Build Dev ECR (./docker/db.Dockerfile, ECR_MIGRATIONS) (push) Has been cancelled
CI / Build Dev ECR (./docker/pii.Dockerfile, ECR_PII) (push) Has been cancelled
CI / Build Dev ECR (./docker/realtime.Dockerfile, ECR_REALTIME) (push) Has been cancelled
CI / Deploy Trigger.dev (Dev) (push) Has been cancelled
CI / Build AMD64 (./docker/app.Dockerfile, ECR_APP, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build AMD64 (./docker/db.Dockerfile, ECR_MIGRATIONS, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build AMD64 (./docker/pii.Dockerfile, ECR_PII, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build AMD64 (./docker/realtime.Dockerfile, ECR_REALTIME, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/app.Dockerfile, ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/db.Dockerfile, ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/pii.Dockerfile, ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Build ARM64 (GHCR Only) (./docker/realtime.Dockerfile, ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/migrations) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/pii) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/realtime) (push) Has been cancelled
CI / Create GHCR Manifests (ghcr.io/simstudioai/simstudio) (push) Has been cancelled
CI / Check Docs Changes (push) Has been cancelled
CI / Process Docs (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
CI / Test and Build (push) Has been cancelled
Publish CLI Package / publish-npm (push) Has been cancelled
Publish Python SDK / publish-pypi (push) Has been cancelled
Publish TypeScript SDK / publish-npm (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:20:55 +08:00
commit d25d482dc2
13754 changed files with 4996608 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
import type { DagsterDeleteRunParams, DagsterDeleteRunResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface DeleteRunResult {
type: string
runId?: string
message?: string
}
const DELETE_RUN_MUTATION = `
mutation DeleteRun($runId: String!) {
deleteRun(runId: $runId) {
type: __typename
... on DeletePipelineRunSuccess {
runId
}
... on RunNotFoundError {
message
}
... on UnauthorizedError {
message
}
... on PythonError {
message
}
}
}
`
export const deleteRunTool: ToolConfig<DagsterDeleteRunParams, DagsterDeleteRunResponse> = {
id: 'dagster_delete_run',
name: 'Dagster Delete Run',
description: 'Permanently delete a Dagster run record.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
runId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the run to delete',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => ({
query: DELETE_RUN_MUTATION,
variables: { runId: params.runId },
}),
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ deleteRun?: unknown }>(response)
const result = data.data?.deleteRun as DeleteRunResult | undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (result.type === 'DeletePipelineRunSuccess' && result.runId) {
return {
success: true,
output: { runId: result.runId },
}
}
throw new Error(`${result.type}: ${dagsterUnionErrorMessage(result, 'Delete run failed')}`)
},
outputs: {
runId: {
type: 'string',
description: 'The ID of the deleted run',
},
},
}
+167
View File
@@ -0,0 +1,167 @@
import type { DagsterGetAssetParams, DagsterGetAssetResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
parseAssetKeyPath,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
/** Fields selected on `assetOrError` when the union resolves to `Asset`. */
interface DagsterGetAssetGraphqlAsset {
key: { path: string[] }
definition: {
groupName: string | null
description: string | null
jobNames: string[] | null
computeKind: string | null
isPartitioned: boolean | null
} | null
assetMaterializations: Array<{
runId: string
timestamp: string
partition: string | null
stepKey: string | null
}> | null
}
const GET_ASSET_QUERY = `
query GetAsset($assetKey: AssetKeyInput!) {
assetOrError(assetKey: $assetKey) {
... on Asset {
key {
path
}
definition {
groupName
description
jobNames
computeKind
isPartitioned
}
assetMaterializations(limit: 1) {
runId
timestamp
partition
stepKey
}
}
... on AssetNotFoundError {
__typename
message
}
}
}
`
export const getAssetTool: ToolConfig<DagsterGetAssetParams, DagsterGetAssetResponse> = {
id: 'dagster_get_asset',
name: 'Dagster Get Asset',
description: 'Get an asset definition and its latest materialization by asset key.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
assetKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Slash-delimited asset key, e.g. "my_asset" or "raw/events"',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => ({
query: GET_ASSET_QUERY,
variables: { assetKey: { path: parseAssetKeyPath(params.assetKey) } },
}),
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ assetOrError?: unknown }>(response)
const raw = data.data?.assetOrError
if (!raw || typeof raw !== 'object') throw new Error('Unexpected response from Dagster')
if (!('key' in raw)) {
const errResult = raw as { message?: string }
throw new Error(errResult.message ?? 'Asset not found')
}
const asset = raw as DagsterGetAssetGraphqlAsset
const latest = asset.assetMaterializations?.[0] ?? null
return {
success: true,
output: {
assetKey: asset.key.path.join('/'),
path: asset.key.path,
groupName: asset.definition?.groupName ?? null,
description: asset.definition?.description ?? null,
jobNames: asset.definition?.jobNames ?? null,
computeKind: asset.definition?.computeKind ?? null,
isPartitioned: asset.definition?.isPartitioned ?? null,
latestMaterialization: latest
? {
runId: latest.runId,
timestamp: latest.timestamp,
partition: latest.partition ?? null,
stepKey: latest.stepKey ?? null,
}
: null,
},
}
},
outputs: {
assetKey: { type: 'string', description: 'Slash-joined asset key' },
path: { type: 'json', description: 'Asset key path segments' },
groupName: {
type: 'string',
description: 'Asset group the definition belongs to',
optional: true,
},
description: { type: 'string', description: 'Asset description', optional: true },
jobNames: {
type: 'json',
description: 'Names of jobs that can materialize this asset',
optional: true,
},
computeKind: {
type: 'string',
description: 'Compute kind tag (e.g., python, dbt, spark)',
optional: true,
},
isPartitioned: {
type: 'boolean',
description: 'Whether the asset is partitioned',
optional: true,
},
latestMaterialization: {
type: 'json',
description: 'Most recent materialization (runId, timestamp, partition, stepKey)',
optional: true,
properties: {
runId: { type: 'string', description: 'Run that produced the materialization' },
timestamp: { type: 'string', description: 'Materialization timestamp (epoch ms string)' },
partition: { type: 'string', description: 'Partition key, if partitioned', optional: true },
stepKey: { type: 'string', description: 'Step key that emitted it', optional: true },
},
},
},
}
+209
View File
@@ -0,0 +1,209 @@
import type { DagsterGetRunParams, DagsterGetRunResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
/** Fields selected on `runOrError` when the union resolves to `Run`. */
interface DagsterGetRunGraphqlRun {
runId: string
jobName: string | null
status: string
mode: string | null
startTime: number | null
endTime: number | null
creationTime: number | null
updateTime: number | null
parentRunId: string | null
rootRunId: string | null
canTerminate: boolean
assetSelection: Array<{ path: string[] }> | null
runConfigYaml: string | null
tags: Array<{ key: string; value: string }> | null
}
const GET_RUN_QUERY = `
query GetRun($runId: ID!) {
runOrError(runId: $runId) {
... on Run {
runId
jobName
status
mode
startTime
endTime
creationTime
updateTime
parentRunId
rootRunId
canTerminate
assetSelection {
path
}
runConfigYaml
tags {
key
value
}
}
... on RunNotFoundError {
__typename
message
}
... on PythonError {
__typename
message
}
}
}
`
export const getRunTool: ToolConfig<DagsterGetRunParams, DagsterGetRunResponse> = {
id: 'dagster_get_run',
name: 'Dagster Get Run',
description: 'Get the status and details of a Dagster run by its ID.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3000)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
runId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the run to retrieve',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => ({
query: GET_RUN_QUERY,
variables: { runId: params.runId },
}),
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ runOrError?: unknown }>(response)
const raw = data.data?.runOrError
if (!raw || typeof raw !== 'object') throw new Error('Unexpected response from Dagster')
if (!('runId' in raw) || typeof (raw as { runId: unknown }).runId !== 'string') {
throw new Error(
dagsterUnionErrorMessage(raw as { message?: string }, 'Run not found or Dagster error')
)
}
const run = raw as DagsterGetRunGraphqlRun
return {
success: true,
output: {
runId: run.runId,
jobName: run.jobName ?? null,
status: run.status,
mode: run.mode ?? null,
startTime: run.startTime ?? null,
endTime: run.endTime ?? null,
creationTime: run.creationTime ?? null,
updateTime: run.updateTime ?? null,
parentRunId: run.parentRunId ?? null,
rootRunId: run.rootRunId ?? null,
canTerminate: run.canTerminate ?? false,
assetSelection: run.assetSelection
? run.assetSelection.map((key) => key.path.join('/'))
: null,
runConfigYaml: run.runConfigYaml ?? null,
tags: run.tags ?? null,
},
}
},
outputs: {
runId: {
type: 'string',
description: 'Run ID',
},
jobName: {
type: 'string',
description: 'Name of the job this run belongs to',
optional: true,
},
status: {
type: 'string',
description:
'Run status (QUEUED, NOT_STARTED, STARTING, MANAGED, STARTED, SUCCESS, FAILURE, CANCELING, CANCELED)',
},
mode: {
type: 'string',
description: 'Execution mode of the run',
optional: true,
},
startTime: {
type: 'number',
description: 'Run start time as Unix timestamp',
optional: true,
},
endTime: {
type: 'number',
description: 'Run end time as Unix timestamp',
optional: true,
},
creationTime: {
type: 'number',
description: 'Time the run was created as Unix timestamp',
optional: true,
},
updateTime: {
type: 'number',
description: 'Time the run was last updated as Unix timestamp',
optional: true,
},
parentRunId: {
type: 'string',
description: 'ID of the immediate parent run (for re-executions)',
optional: true,
},
rootRunId: {
type: 'string',
description: 'ID of the root run in the re-execution group',
optional: true,
},
canTerminate: {
type: 'boolean',
description: 'Whether the run can currently be terminated',
},
assetSelection: {
type: 'json',
description: 'Asset keys targeted by the run, as slash-joined strings',
optional: true,
},
runConfigYaml: {
type: 'string',
description: 'Run configuration as YAML',
optional: true,
},
tags: {
type: 'json',
description: 'Run tags as array of {key, value} objects',
optional: true,
},
},
}
+166
View File
@@ -0,0 +1,166 @@
import type { DagsterGetRunLogsParams, DagsterGetRunLogsResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface DagsterRunEvent {
__typename?: string
message?: string
timestamp?: string
level?: string
stepKey?: string | null
eventType?: string | null
}
interface DagsterEventConnection {
events?: DagsterRunEvent[]
cursor?: string
hasMore?: boolean
}
const GET_RUN_LOGS_QUERY = `
query GetRunLogs($runId: ID!, $afterCursor: String, $limit: Int) {
logsForRun(runId: $runId, afterCursor: $afterCursor, limit: $limit) {
... on EventConnection {
events {
__typename
... on MessageEvent {
message
timestamp
level
stepKey
eventType
}
}
cursor
hasMore
}
... on RunNotFoundError {
__typename
message
}
... on PythonError {
__typename
message
}
}
}
`
export const getRunLogsTool: ToolConfig<DagsterGetRunLogsParams, DagsterGetRunLogsResponse> = {
id: 'dagster_get_run_logs',
name: 'Dagster Get Run Logs',
description: 'Fetch execution event logs for a Dagster run.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
runId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the run to fetch logs for',
},
afterCursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Cursor for paginating through log events (from a previous response)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of log events to return',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => {
const variables: Record<string, unknown> = { runId: params.runId }
if (params.afterCursor) variables.afterCursor = params.afterCursor
if (params.limit != null) variables.limit = params.limit
return { query: GET_RUN_LOGS_QUERY, variables }
},
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ logsForRun?: unknown }>(response)
const result = data.data?.logsForRun as
| DagsterEventConnection
| { message?: string }
| undefined
if (!result || typeof result !== 'object') throw new Error('Unexpected response from Dagster')
if (!('events' in result)) {
const errResult = result as { message?: string }
throw new Error(errResult.message ?? 'Failed to fetch run logs')
}
const conn = result as DagsterEventConnection
const events = (conn.events ?? []).map((e) => ({
type: e.__typename ?? 'Unknown',
message: e.message ?? '',
timestamp: e.timestamp ?? '',
level: e.level ?? 'INFO',
stepKey: e.stepKey ?? null,
eventType: e.eventType ?? null,
}))
return {
success: true,
output: {
events,
cursor: conn.cursor ?? null,
hasMore: conn.hasMore ?? false,
},
}
},
outputs: {
events: {
type: 'json',
description: 'Array of log events (type, message, timestamp, level, stepKey, eventType)',
properties: {
type: { type: 'string', description: 'GraphQL typename of the event' },
message: { type: 'string', description: 'Human-readable log message' },
timestamp: { type: 'string', description: 'Event timestamp as a Unix epoch string' },
level: { type: 'string', description: 'Log level (DEBUG, INFO, WARNING, ERROR, CRITICAL)' },
stepKey: {
type: 'string',
description: 'Step key, if the event is step-scoped',
optional: true,
},
eventType: { type: 'string', description: 'Dagster event type enum value', optional: true },
},
},
cursor: {
type: 'string',
description: 'Cursor for fetching the next page of log events',
optional: true,
},
hasMore: {
type: 'boolean',
description: 'Whether more log events are available beyond this page',
},
},
}
+41
View File
@@ -0,0 +1,41 @@
import { deleteRunTool } from '@/tools/dagster/delete_run'
import { getAssetTool } from '@/tools/dagster/get_asset'
import { getRunTool } from '@/tools/dagster/get_run'
import { getRunLogsTool } from '@/tools/dagster/get_run_logs'
import { launchRunTool } from '@/tools/dagster/launch_run'
import { listAssetsTool } from '@/tools/dagster/list_assets'
import { listJobsTool } from '@/tools/dagster/list_jobs'
import { listRunsTool } from '@/tools/dagster/list_runs'
import { listSchedulesTool } from '@/tools/dagster/list_schedules'
import { listSensorsTool } from '@/tools/dagster/list_sensors'
import { materializeAssetsTool } from '@/tools/dagster/materialize_assets'
import { reexecuteRunTool } from '@/tools/dagster/reexecute_run'
import { reportAssetMaterializationTool } from '@/tools/dagster/report_asset_materialization'
import { startScheduleTool } from '@/tools/dagster/start_schedule'
import { startSensorTool } from '@/tools/dagster/start_sensor'
import { stopScheduleTool } from '@/tools/dagster/stop_schedule'
import { stopSensorTool } from '@/tools/dagster/stop_sensor'
import { terminateRunTool } from '@/tools/dagster/terminate_run'
import { wipeAssetTool } from '@/tools/dagster/wipe_asset'
export const dagsterLaunchRunTool = launchRunTool
export const dagsterGetRunTool = getRunTool
export const dagsterListRunsTool = listRunsTool
export const dagsterListJobsTool = listJobsTool
export const dagsterTerminateRunTool = terminateRunTool
export const dagsterGetRunLogsTool = getRunLogsTool
export const dagsterReexecuteRunTool = reexecuteRunTool
export const dagsterDeleteRunTool = deleteRunTool
export const dagsterListSchedulesTool = listSchedulesTool
export const dagsterStartScheduleTool = startScheduleTool
export const dagsterStopScheduleTool = stopScheduleTool
export const dagsterListSensorsTool = listSensorsTool
export const dagsterStartSensorTool = startSensorTool
export const dagsterStopSensorTool = stopSensorTool
export const dagsterListAssetsTool = listAssetsTool
export const dagsterGetAssetTool = getAssetTool
export const dagsterMaterializeAssetsTool = materializeAssetsTool
export const dagsterReportAssetMaterializationTool = reportAssetMaterializationTool
export const dagsterWipeAssetTool = wipeAssetTool
export * from './types'
+223
View File
@@ -0,0 +1,223 @@
import type { DagsterLaunchRunParams, DagsterLaunchRunResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface LaunchRunResult {
type: string
run?: { runId: string }
message?: string
/** Present when type === 'InvalidStepError' */
invalidStepKey?: string
/** Present when type === 'InvalidOutputError' */
stepKey?: string
invalidOutputName?: string
/** Present when type === 'RunConfigValidationInvalid' */
errors?: Array<{ message: string }>
}
function buildLaunchRunMutation(hasConfig: boolean, hasTags: boolean) {
const varDefs = [
'$repositoryLocationName: String!',
'$repositoryName: String!',
'$jobName: String!',
]
if (hasConfig) varDefs.push('$runConfigData: RunConfigData')
if (hasTags) varDefs.push('$tags: [ExecutionTag!]')
const execParams = [
`selector: {
repositoryLocationName: $repositoryLocationName
repositoryName: $repositoryName
jobName: $jobName
}`,
]
if (hasConfig) execParams.push('runConfigData: $runConfigData')
if (hasTags) execParams.push('executionMetadata: { tags: $tags }')
return `
mutation LaunchRun(${varDefs.join(', ')}) {
launchRun(
executionParams: {
${execParams.join('\n ')}
}
) {
type: __typename
... on LaunchRunSuccess {
run {
runId
}
}
... on InvalidStepError {
__typename
invalidStepKey
}
... on InvalidOutputError {
__typename
stepKey
invalidOutputName
}
... on RunConfigValidationInvalid {
errors {
message
}
}
... on PipelineNotFoundError {
message
}
... on RunConflict {
message
}
... on UnauthorizedError {
message
}
... on InvalidSubsetError {
message
}
... on PresetNotFoundError {
message
}
... on ConflictingExecutionParamsError {
message
}
... on NoModeProvidedError {
message
}
... on PythonError {
message
}
}
}
`
}
export const launchRunTool: ToolConfig<DagsterLaunchRunParams, DagsterLaunchRunResponse> = {
id: 'dagster_launch_run',
name: 'Dagster Launch Run',
description: 'Launch a job run on a Dagster instance.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3000)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
repositoryLocationName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository location (code location) name',
},
repositoryName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name within the code location',
},
jobName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the job to launch',
},
runConfigJson: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Run configuration as a JSON object (optional)',
},
tags: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Tags as a JSON array of {key, value} objects (optional)',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => {
const variables: Record<string, unknown> = {
repositoryLocationName: params.repositoryLocationName,
repositoryName: params.repositoryName,
jobName: params.jobName,
}
let hasConfig = false
if (params.runConfigJson) {
try {
variables.runConfigData = JSON.parse(params.runConfigJson)
hasConfig = true
} catch {
throw new Error('Invalid JSON in runConfigJson')
}
}
let hasTags = false
if (params.tags) {
try {
variables.tags = JSON.parse(params.tags)
hasTags = true
} catch {
throw new Error('Invalid JSON in tags')
}
}
return {
query: buildLaunchRunMutation(hasConfig, hasTags),
variables,
}
},
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ launchRun?: unknown }>(response)
const result = data.data?.launchRun as LaunchRunResult | undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (result.type === 'LaunchRunSuccess' && result.run) {
return {
success: true,
output: { runId: result.run.runId },
}
}
if (result.type === 'InvalidStepError' && result.invalidStepKey) {
throw new Error(`InvalidStepError: invalid step key "${result.invalidStepKey}"`)
}
if (result.type === 'InvalidOutputError' && result.stepKey) {
throw new Error(
`InvalidOutputError: invalid output "${result.invalidOutputName ?? 'unknown'}" on step "${result.stepKey}"`
)
}
if (result.type === 'RunConfigValidationInvalid' && result.errors?.length) {
throw new Error(
`RunConfigValidationInvalid: ${result.errors.map((e) => e.message).join('; ')}`
)
}
throw new Error(`${result.type}: ${dagsterUnionErrorMessage(result, 'Launch run failed')}`)
},
outputs: {
runId: {
type: 'string',
description: 'The globally unique ID of the launched run',
},
},
}
+147
View File
@@ -0,0 +1,147 @@
import type { DagsterListAssetsParams, DagsterListAssetsResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseAssetKeyPath,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
/** Default page size applied when the caller omits `limit`, so paging stays bounded and `hasMore` is meaningful. */
const DEFAULT_LIST_ASSETS_LIMIT = 100
/** Shape of each asset node in the `assetsOrError` → `AssetConnection.nodes` selection set. */
interface DagsterAssetGraphqlNode {
key: { path: string[] }
}
const LIST_ASSETS_QUERY = `
query ListAssets($cursor: String, $limit: Int, $prefix: [String!]) {
assetsOrError(cursor: $cursor, limit: $limit, prefix: $prefix) {
... on AssetConnection {
nodes {
key {
path
}
}
cursor
}
... on PythonError {
__typename
message
}
}
}
`
export const listAssetsTool: ToolConfig<DagsterListAssetsParams, DagsterListAssetsResponse> = {
id: 'dagster_list_assets',
name: 'Dagster List Assets',
description: 'List assets tracked by a Dagster instance, optionally filtered by key prefix.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
prefix: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description:
'Slash-delimited asset key prefix to filter by, e.g. "raw" or "raw/events" (optional)',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Asset key cursor from a previous response, for pagination (optional)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of assets to return per page (default 100)',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => {
const pageSize = params.limit ?? DEFAULT_LIST_ASSETS_LIMIT
// Request one extra row so `hasMore` is exact even when the final page is exactly `pageSize` long.
const variables: Record<string, unknown> = { limit: pageSize + 1 }
if (params.prefix) variables.prefix = parseAssetKeyPath(params.prefix)
if (params.cursor) variables.cursor = params.cursor
return { query: LIST_ASSETS_QUERY, variables }
},
},
transformResponse: async (response: Response, params?: DagsterListAssetsParams) => {
const data = await parseDagsterGraphqlResponse<{ assetsOrError?: unknown }>(response)
const result = data.data?.assetsOrError as
| { nodes?: DagsterAssetGraphqlNode[]; cursor?: string | null; message?: string }
| undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (!Array.isArray(result.nodes)) {
throw new Error(dagsterUnionErrorMessage(result, 'List assets failed'))
}
const pageSize = params?.limit ?? DEFAULT_LIST_ASSETS_LIMIT
const hasMore = result.nodes.length > pageSize
const pageNodes = hasMore ? result.nodes.slice(0, pageSize) : result.nodes
const assets = pageNodes.map((node) => ({
assetKey: node.key.path.join('/'),
path: node.key.path,
}))
// Asset cursors are the JSON-serialized key path; Dagster normalizes JS/Python whitespace on the
// way back in, so we derive the cursor from the last RETURNED asset (not the extra probe row).
const lastPath = pageNodes.length > 0 ? pageNodes[pageNodes.length - 1].key.path : null
return {
success: true,
output: {
assets,
cursor: lastPath ? JSON.stringify(lastPath) : null,
hasMore,
},
}
},
outputs: {
assets: {
type: 'json',
description: 'Array of assets (assetKey, path)',
properties: {
assetKey: { type: 'string', description: 'Slash-joined asset key' },
path: { type: 'json', description: 'Asset key path segments' },
},
},
cursor: {
type: 'string',
description: 'Cursor to pass on the next call to fetch more assets',
optional: true,
},
hasMore: {
type: 'boolean',
description: 'Whether more assets are likely available beyond this page',
},
},
}
+104
View File
@@ -0,0 +1,104 @@
import type { DagsterBaseParams, DagsterListJobsResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
const LIST_JOBS_QUERY = `
query ListJobNames {
repositoriesOrError {
... on RepositoryConnection {
nodes {
name
jobs {
name
}
}
}
... on RepositoryNotFoundError {
__typename
message
}
... on PythonError {
__typename
message
}
}
}
`
export const listJobsTool: ToolConfig<DagsterBaseParams, DagsterListJobsResponse> = {
id: 'dagster_list_jobs',
name: 'Dagster List Jobs',
description: 'List all jobs across repositories in a Dagster instance.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: () => ({
query: LIST_JOBS_QUERY,
variables: {},
}),
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ repositoriesOrError?: unknown }>(response)
const result = data.data?.repositoriesOrError as
| { nodes?: Array<{ name: string; jobs?: Array<{ name: string }> }>; message?: string }
| undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (!Array.isArray(result.nodes)) {
throw new Error(dagsterUnionErrorMessage(result, 'List jobs failed'))
}
const jobs: Array<{ name: string; repositoryName: string }> = []
for (const repo of result.nodes) {
for (const job of repo.jobs ?? []) {
jobs.push({
name: job.name,
repositoryName: repo.name,
})
}
}
return {
success: true,
output: { jobs },
}
},
outputs: {
jobs: {
type: 'json',
description: 'Array of jobs with name and repositoryName',
properties: {
name: { type: 'string', description: 'Job name' },
repositoryName: { type: 'string', description: 'Repository name' },
},
},
},
}
+201
View File
@@ -0,0 +1,201 @@
import type { DagsterListRunsParams, DagsterListRunsResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
/** Default page size applied when the caller omits `limit`, so paging stays bounded and `hasMore` is meaningful. */
const DEFAULT_LIST_RUNS_LIMIT = 20
/** Shape of each run in the `runsOrError` → `Runs.results` GraphQL selection set. */
interface DagsterListRunsGraphqlRow {
runId: string
jobName: string | null
status: string
tags: Array<{ key: string; value: string }> | null
startTime: number | null
endTime: number | null
}
function buildListRunsQuery(hasFilter: boolean) {
return `
query ListRuns($limit: Int, $cursor: String${hasFilter ? ', $filter: RunsFilter' : ''}) {
runsOrError(limit: $limit, cursor: $cursor${hasFilter ? ', filter: $filter' : ''}) {
... on Runs {
results {
runId
jobName
status
tags {
key
value
}
startTime
endTime
}
}
... on InvalidPipelineRunsFilterError {
__typename
message
}
... on PythonError {
__typename
message
}
}
}
`
}
export const listRunsTool: ToolConfig<DagsterListRunsParams, DagsterListRunsResponse> = {
id: 'dagster_list_runs',
name: 'Dagster List Runs',
description:
'List Dagster runs with optional filters by job name, status, and creation-time range, plus cursor pagination.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
jobName: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter runs by job name (optional)',
},
statuses: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated run statuses to filter by, e.g. "SUCCESS,FAILURE" (optional)',
},
createdAfter: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Only return runs created at or after this Unix timestamp in seconds (optional)',
},
createdBefore: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description:
'Only return runs created at or before this Unix timestamp in seconds (optional)',
},
cursor: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Run ID to page after, from a previous response cursor (optional)',
},
limit: {
type: 'number',
required: false,
visibility: 'user-or-llm',
description: 'Maximum number of runs to return (default 20)',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => {
const filter: Record<string, unknown> = {}
if (params.jobName) filter.pipelineName = params.jobName
if (params.statuses) {
filter.statuses = params.statuses
.split(',')
.map((s: string) => s.trim())
.filter(Boolean)
}
if (params.createdAfter != null) filter.createdAfter = params.createdAfter
if (params.createdBefore != null) filter.createdBefore = params.createdBefore
const hasFilter = Object.keys(filter).length > 0
const pageSize = params.limit || DEFAULT_LIST_RUNS_LIMIT
// Request one extra row so `hasMore` is exact even when the final page is exactly `pageSize` long.
const variables: Record<string, unknown> = { limit: pageSize + 1 }
if (params.cursor) variables.cursor = params.cursor
if (hasFilter) variables.filter = filter
return {
query: buildListRunsQuery(hasFilter),
variables,
}
},
},
transformResponse: async (response: Response, params?: DagsterListRunsParams) => {
const data = await parseDagsterGraphqlResponse<{ runsOrError?: unknown }>(response)
const result = data.data?.runsOrError as
| { results?: DagsterListRunsGraphqlRow[]; message?: string }
| undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (!Array.isArray(result.results)) {
throw new Error(dagsterUnionErrorMessage(result, 'Dagster returned an error listing runs'))
}
const pageSize = params?.limit || DEFAULT_LIST_RUNS_LIMIT
const hasMore = result.results.length > pageSize
const pageRows = hasMore ? result.results.slice(0, pageSize) : result.results
const runs = pageRows.map((r: DagsterListRunsGraphqlRow) => ({
runId: r.runId,
jobName: r.jobName ?? null,
status: r.status,
tags: r.tags ?? null,
startTime: r.startTime ?? null,
endTime: r.endTime ?? null,
}))
return {
success: true,
output: {
runs,
cursor: runs.length > 0 ? runs[runs.length - 1].runId : null,
hasMore,
},
}
},
outputs: {
runs: {
type: 'json',
description: 'Array of runs',
properties: {
runId: { type: 'string', description: 'Run ID' },
jobName: { type: 'string', description: 'Job name' },
status: { type: 'string', description: 'Run status' },
tags: { type: 'json', description: 'Run tags as array of {key, value} objects' },
startTime: { type: 'number', description: 'Start time as Unix timestamp' },
endTime: { type: 'number', description: 'End time as Unix timestamp' },
},
},
cursor: {
type: 'string',
description: 'Run ID of the last returned run — pass as cursor to fetch the next page',
optional: true,
},
hasMore: {
type: 'boolean',
description: 'Whether more runs are likely available beyond this page',
},
},
}
+162
View File
@@ -0,0 +1,162 @@
import type {
DagsterListSchedulesParams,
DagsterListSchedulesResponse,
} from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface DagsterScheduleGraphql {
name: string
cronSchedule: string | null
pipelineName: string | null
description: string | null
executionTimezone: string | null
scheduleState?: {
id: string
status: string
} | null
}
function buildListSchedulesQuery(hasStatus: boolean) {
return `
query ListSchedules($repositorySelector: RepositorySelector!${hasStatus ? ', $scheduleStatus: InstigationStatus' : ''}) {
schedulesOrError(repositorySelector: $repositorySelector${hasStatus ? ', scheduleStatus: $scheduleStatus' : ''}) {
... on Schedules {
results {
name
cronSchedule
pipelineName
description
executionTimezone
scheduleState {
id
status
}
}
}
... on RepositoryNotFoundError {
__typename
message
}
... on PythonError {
__typename
message
}
}
}
`
}
export const listSchedulesTool: ToolConfig<
DagsterListSchedulesParams,
DagsterListSchedulesResponse
> = {
id: 'dagster_list_schedules',
name: 'Dagster List Schedules',
description: 'List all schedules in a Dagster repository, optionally filtered by status.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
repositoryLocationName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository location (code location) name',
},
repositoryName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name within the code location',
},
scheduleStatus: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter schedules by status: RUNNING or STOPPED (omit to return all)',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => {
const hasStatus = Boolean(params.scheduleStatus)
const variables: Record<string, unknown> = {
repositorySelector: {
repositoryLocationName: params.repositoryLocationName,
repositoryName: params.repositoryName,
},
}
if (hasStatus) variables.scheduleStatus = params.scheduleStatus
return { query: buildListSchedulesQuery(hasStatus), variables }
},
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ schedulesOrError?: unknown }>(response)
const result = data.data?.schedulesOrError as
| { results?: DagsterScheduleGraphql[]; message?: string }
| undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (!Array.isArray(result.results)) {
throw new Error(dagsterUnionErrorMessage(result, 'List schedules failed'))
}
const schedules = result.results.map((s) => ({
name: s.name,
cronSchedule: s.cronSchedule ?? null,
jobName: s.pipelineName ?? null,
status: s.scheduleState?.status ?? 'UNKNOWN',
id: s.scheduleState?.id ?? null,
description: s.description ?? null,
executionTimezone: s.executionTimezone ?? null,
}))
return {
success: true,
output: { schedules },
}
},
outputs: {
schedules: {
type: 'json',
description:
'Array of schedules (name, cronSchedule, jobName, status, id, description, executionTimezone)',
properties: {
name: { type: 'string', description: 'Schedule name' },
cronSchedule: { type: 'string', description: 'Cron expression for the schedule' },
jobName: { type: 'string', description: 'Job the schedule targets' },
status: { type: 'string', description: 'Schedule status: RUNNING or STOPPED' },
id: {
type: 'string',
description: 'Instigator state ID — use this to start or stop the schedule',
},
description: { type: 'string', description: 'Human-readable schedule description' },
executionTimezone: { type: 'string', description: 'Timezone for cron evaluation' },
},
},
},
}
+151
View File
@@ -0,0 +1,151 @@
import type { DagsterListSensorsParams, DagsterListSensorsResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface DagsterSensorGraphql {
name: string
sensorType: string | null
description: string | null
sensorState?: {
id: string
status: string
} | null
}
function buildListSensorsQuery(hasStatus: boolean) {
return `
query ListSensors($repositorySelector: RepositorySelector!${hasStatus ? ', $sensorStatus: InstigationStatus' : ''}) {
sensorsOrError(repositorySelector: $repositorySelector${hasStatus ? ', sensorStatus: $sensorStatus' : ''}) {
... on Sensors {
results {
name
sensorType
description
sensorState {
id
status
}
}
}
... on RepositoryNotFoundError {
__typename
message
}
... on PythonError {
__typename
message
}
}
}
`
}
export const listSensorsTool: ToolConfig<DagsterListSensorsParams, DagsterListSensorsResponse> = {
id: 'dagster_list_sensors',
name: 'Dagster List Sensors',
description: 'List all sensors in a Dagster repository, optionally filtered by status.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
repositoryLocationName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository location (code location) name',
},
repositoryName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name within the code location',
},
sensorStatus: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Filter sensors by status: RUNNING or STOPPED (omit to return all)',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => {
const hasStatus = Boolean(params.sensorStatus)
const variables: Record<string, unknown> = {
repositorySelector: {
repositoryLocationName: params.repositoryLocationName,
repositoryName: params.repositoryName,
},
}
if (hasStatus) variables.sensorStatus = params.sensorStatus
return { query: buildListSensorsQuery(hasStatus), variables }
},
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ sensorsOrError?: unknown }>(response)
const result = data.data?.sensorsOrError as
| { results?: DagsterSensorGraphql[]; message?: string }
| undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (!Array.isArray(result.results)) {
throw new Error(dagsterUnionErrorMessage(result, 'List sensors failed'))
}
const sensors = result.results.map((s) => ({
name: s.name,
sensorType: s.sensorType ?? null,
status: s.sensorState?.status ?? 'UNKNOWN',
id: s.sensorState?.id ?? null,
description: s.description ?? null,
}))
return {
success: true,
output: { sensors },
}
},
outputs: {
sensors: {
type: 'json',
description: 'Array of sensors (name, sensorType, status, id, description)',
properties: {
name: { type: 'string', description: 'Sensor name' },
sensorType: {
type: 'string',
description:
'Sensor type (ASSET, AUTO_MATERIALIZE, FRESHNESS_POLICY, MULTI_ASSET, RUN_STATUS, STANDARD, UNKNOWN)',
},
status: { type: 'string', description: 'Sensor status: RUNNING or STOPPED' },
id: {
type: 'string',
description: 'Instigator state ID — use this to start or stop the sensor',
},
description: { type: 'string', description: 'Human-readable sensor description' },
},
},
},
}
@@ -0,0 +1,201 @@
import type {
DagsterMaterializeAssetsParams,
DagsterMaterializeAssetsResponse,
} from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseAssetSelection,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface MaterializeAssetsResult {
type: string
run?: { runId: string }
message?: string
errors?: Array<{ message: string }>
}
function buildMaterializeMutation(hasTags: boolean) {
const varDefs = [
'$repositoryLocationName: String!',
'$repositoryName: String!',
'$jobName: String!',
'$assetSelection: [AssetKeyInput!]',
]
if (hasTags) varDefs.push('$tags: [ExecutionTag!]')
const execParams = [
`selector: {
repositoryLocationName: $repositoryLocationName
repositoryName: $repositoryName
jobName: $jobName
assetSelection: $assetSelection
}`,
]
if (hasTags) execParams.push('executionMetadata: { tags: $tags }')
return `
mutation MaterializeAssets(${varDefs.join(', ')}) {
launchRun(
executionParams: {
${execParams.join('\n ')}
}
) {
type: __typename
... on LaunchRunSuccess {
run {
runId
}
}
... on RunConfigValidationInvalid {
errors {
message
}
}
... on PipelineNotFoundError {
message
}
... on InvalidSubsetError {
message
}
... on UnauthorizedError {
message
}
... on ConflictingExecutionParamsError {
message
}
... on PresetNotFoundError {
message
}
... on RunConflict {
message
}
... on PythonError {
message
}
}
}
`
}
export const materializeAssetsTool: ToolConfig<
DagsterMaterializeAssetsParams,
DagsterMaterializeAssetsResponse
> = {
id: 'dagster_materialize_assets',
name: 'Dagster Materialize Assets',
description: 'Materialize selected assets by launching their asset job with an asset selection.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
repositoryLocationName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository location (code location) name',
},
repositoryName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name within the code location',
},
jobName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Asset job that contains the assets, e.g. "__ASSET_JOB" or a named asset job',
},
assetSelection: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Comma- or newline-separated asset keys to materialize, each slash-delimited (e.g. "raw/events, summary")',
},
tags: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Tags as a JSON array of {key, value} objects (optional)',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => {
const assetSelection = parseAssetSelection(params.assetSelection)
if (assetSelection.length === 0) {
throw new Error('assetSelection must contain at least one asset key')
}
const variables: Record<string, unknown> = {
repositoryLocationName: params.repositoryLocationName,
repositoryName: params.repositoryName,
jobName: params.jobName,
assetSelection,
}
let hasTags = false
if (params.tags) {
try {
variables.tags = JSON.parse(params.tags)
hasTags = true
} catch {
throw new Error('Invalid JSON in tags')
}
}
return { query: buildMaterializeMutation(hasTags), variables }
},
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ launchRun?: unknown }>(response)
const result = data.data?.launchRun as MaterializeAssetsResult | undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (result.type === 'LaunchRunSuccess' && result.run) {
return {
success: true,
output: { runId: result.run.runId },
}
}
if (result.type === 'RunConfigValidationInvalid' && result.errors?.length) {
throw new Error(
`RunConfigValidationInvalid: ${result.errors.map((e) => e.message).join('; ')}`
)
}
throw new Error(
`${result.type}: ${dagsterUnionErrorMessage(result, 'Materialize assets failed')}`
)
},
outputs: {
runId: {
type: 'string',
description: 'The globally unique ID of the launched materialization run',
},
},
}
+159
View File
@@ -0,0 +1,159 @@
import type { DagsterReexecuteRunParams, DagsterReexecuteRunResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface ReexecuteRunResult {
type: string
run?: { runId: string }
message?: string
/** Returned by InvalidStepError */
invalidStepKey?: string
/** Returned by InvalidOutputError */
invalidOutputName?: string
stepKey?: string
/** Returned by RunConfigValidationInvalid */
errors?: Array<{ message: string }>
}
const REEXECUTE_RUN_MUTATION = `
mutation LaunchRunReexecution($parentRunId: String!, $strategy: ReexecutionStrategy!) {
launchRunReexecution(
reexecutionParams: {
parentRunId: $parentRunId
strategy: $strategy
}
) {
type: __typename
... on LaunchRunSuccess {
run {
runId
}
}
... on InvalidStepError {
invalidStepKey
}
... on InvalidOutputError {
stepKey
invalidOutputName
}
... on RunConfigValidationInvalid {
errors {
message
}
}
... on PipelineNotFoundError {
message
}
... on RunConflict {
message
}
... on UnauthorizedError {
message
}
... on InvalidSubsetError {
message
}
... on PresetNotFoundError {
message
}
... on ConflictingExecutionParamsError {
message
}
... on NoModeProvidedError {
message
}
... on PythonError {
message
}
}
}
`
export const reexecuteRunTool: ToolConfig<DagsterReexecuteRunParams, DagsterReexecuteRunResponse> =
{
id: 'dagster_reexecute_run',
name: 'Dagster Reexecute Run',
description: 'Reexecute an existing Dagster run, optionally resuming only from failed steps.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
parentRunId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the run to reexecute',
},
strategy: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'Reexecution strategy: ALL_STEPS reruns everything, FROM_FAILURE resumes from failed steps, FROM_ASSET_FAILURE resumes from failed assets',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => ({
query: REEXECUTE_RUN_MUTATION,
variables: {
parentRunId: params.parentRunId,
strategy: params.strategy,
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ launchRunReexecution?: unknown }>(response)
const result = data.data?.launchRunReexecution as ReexecuteRunResult | undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (result.type === 'LaunchRunSuccess' && result.run) {
return {
success: true,
output: { runId: result.run.runId },
}
}
let detail: string
if (result.type === 'InvalidStepError' && result.invalidStepKey) {
detail = `Invalid step key: ${result.invalidStepKey}`
} else if (result.type === 'InvalidOutputError' && result.invalidOutputName) {
detail = `Invalid output "${result.invalidOutputName}" on step "${result.stepKey}"`
} else if (result.type === 'RunConfigValidationInvalid' && result.errors?.length) {
detail = result.errors.map((e) => e.message).join('; ')
} else {
detail = dagsterUnionErrorMessage(result, 'Reexecute run failed')
}
throw new Error(`${result.type}: ${detail}`)
},
outputs: {
runId: {
type: 'string',
description: 'The ID of the newly launched reexecution run',
},
},
}
@@ -0,0 +1,140 @@
import type {
DagsterReportAssetMaterializationParams,
DagsterReportAssetMaterializationResponse,
} from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseAssetKeyPath,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface ReportAssetEventResult {
type: string
assetKey?: { path: string[] }
message?: string
}
const REPORT_ASSET_EVENT_MUTATION = `
mutation ReportRunlessAssetEvents($eventParams: ReportRunlessAssetEventsParams!) {
reportRunlessAssetEvents(eventParams: $eventParams) {
type: __typename
... on ReportRunlessAssetEventsSuccess {
assetKey {
path
}
}
... on UnauthorizedError {
message
}
... on PythonError {
message
}
}
}
`
export const reportAssetMaterializationTool: ToolConfig<
DagsterReportAssetMaterializationParams,
DagsterReportAssetMaterializationResponse
> = {
id: 'dagster_report_asset_materialization',
name: 'Dagster Report Asset Materialization',
description: 'Report an external (runless) materialization or observation for an asset.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
assetKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Slash-delimited asset key to report against, e.g. "my_asset" or "raw/events"',
},
eventType: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Event type to report: ASSET_MATERIALIZATION (default) or ASSET_OBSERVATION',
},
partitionKeys: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Comma-separated partition keys to report against (optional)',
},
description: {
type: 'string',
required: false,
visibility: 'user-or-llm',
description: 'Human-readable description for the reported event (optional)',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => {
const eventParams: Record<string, unknown> = {
eventType: params.eventType || 'ASSET_MATERIALIZATION',
assetKey: { path: parseAssetKeyPath(params.assetKey) },
}
if (params.partitionKeys) {
eventParams.partitionKeys = params.partitionKeys
.split(',')
.map((key) => key.trim())
.filter(Boolean)
}
if (params.description) eventParams.description = params.description
return { query: REPORT_ASSET_EVENT_MUTATION, variables: { eventParams } }
},
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ reportRunlessAssetEvents?: unknown }>(response)
const result = data.data?.reportRunlessAssetEvents as ReportAssetEventResult | undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (result.type === 'ReportRunlessAssetEventsSuccess' && result.assetKey) {
return {
success: true,
output: {
success: true,
assetKey: result.assetKey.path.join('/'),
},
}
}
throw new Error(
`${result.type}: ${dagsterUnionErrorMessage(result, 'Report asset event failed')}`
)
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the event was reported successfully',
},
assetKey: {
type: 'string',
description: 'Slash-joined asset key the event was reported against',
},
},
}
+136
View File
@@ -0,0 +1,136 @@
import type {
DagsterScheduleMutationResponse,
DagsterStartScheduleParams,
} from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface ScheduleMutationResult {
type: string
scheduleState?: {
id: string
status: string
}
message?: string
}
const START_SCHEDULE_MUTATION = `
mutation StartSchedule($scheduleSelector: ScheduleSelector!) {
startSchedule(scheduleSelector: $scheduleSelector) {
type: __typename
... on ScheduleStateResult {
scheduleState {
id
status
}
}
... on UnauthorizedError {
__typename
message
}
... on ScheduleNotFoundError {
__typename
message
}
... on PythonError {
__typename
message
}
}
}
`
export const startScheduleTool: ToolConfig<
DagsterStartScheduleParams,
DagsterScheduleMutationResponse
> = {
id: 'dagster_start_schedule',
name: 'Dagster Start Schedule',
description: 'Enable (start) a schedule in a Dagster repository.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
repositoryLocationName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository location (code location) name',
},
repositoryName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name within the code location',
},
scheduleName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the schedule to start',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => ({
query: START_SCHEDULE_MUTATION,
variables: {
scheduleSelector: {
repositoryLocationName: params.repositoryLocationName,
repositoryName: params.repositoryName,
scheduleName: params.scheduleName,
},
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ startSchedule?: unknown }>(response)
const result = data.data?.startSchedule as ScheduleMutationResult | undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (result.type === 'ScheduleStateResult' && result.scheduleState) {
return {
success: true,
output: {
id: result.scheduleState.id,
status: result.scheduleState.status,
},
}
}
throw new Error(`${result.type}: ${dagsterUnionErrorMessage(result, 'Start schedule failed')}`)
},
outputs: {
id: {
type: 'string',
description: 'Instigator state ID of the schedule',
},
status: {
type: 'string',
description: 'Updated schedule status (RUNNING or STOPPED)',
},
},
}
+131
View File
@@ -0,0 +1,131 @@
import type { DagsterSensorMutationResponse, DagsterStartSensorParams } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface SensorMutationResult {
type: string
sensorState?: {
id: string
status: string
}
message?: string
}
const START_SENSOR_MUTATION = `
mutation StartSensor($sensorSelector: SensorSelector!) {
startSensor(sensorSelector: $sensorSelector) {
type: __typename
... on Sensor {
sensorState {
id
status
}
}
... on SensorNotFoundError {
__typename
message
}
... on UnauthorizedError {
__typename
message
}
... on PythonError {
__typename
message
}
}
}
`
export const startSensorTool: ToolConfig<DagsterStartSensorParams, DagsterSensorMutationResponse> =
{
id: 'dagster_start_sensor',
name: 'Dagster Start Sensor',
description: 'Enable (start) a sensor in a Dagster repository.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
repositoryLocationName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository location (code location) name',
},
repositoryName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Repository name within the code location',
},
sensorName: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Name of the sensor to start',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => ({
query: START_SENSOR_MUTATION,
variables: {
sensorSelector: {
repositoryLocationName: params.repositoryLocationName,
repositoryName: params.repositoryName,
sensorName: params.sensorName,
},
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ startSensor?: unknown }>(response)
const result = data.data?.startSensor as SensorMutationResult | undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (result.type === 'Sensor' && result.sensorState) {
return {
success: true,
output: {
id: result.sensorState.id,
status: result.sensorState.status,
},
}
}
throw new Error(`${result.type}: ${dagsterUnionErrorMessage(result, 'Start sensor failed')}`)
},
outputs: {
id: {
type: 'string',
description: 'Instigator state ID of the sensor',
},
status: {
type: 'string',
description: 'Updated sensor status (RUNNING or STOPPED)',
},
},
}
+119
View File
@@ -0,0 +1,119 @@
import type {
DagsterScheduleMutationResponse,
DagsterStopScheduleParams,
} from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface ScheduleMutationResult {
type: string
scheduleState?: {
id: string
status: string
}
message?: string
}
const STOP_SCHEDULE_MUTATION = `
mutation StopSchedule($id: String) {
stopRunningSchedule(id: $id) {
type: __typename
... on ScheduleStateResult {
scheduleState {
id
status
}
}
... on UnauthorizedError {
__typename
message
}
... on ScheduleNotFoundError {
__typename
message
}
... on PythonError {
__typename
message
}
}
}
`
export const stopScheduleTool: ToolConfig<
DagsterStopScheduleParams,
DagsterScheduleMutationResponse
> = {
id: 'dagster_stop_schedule',
name: 'Dagster Stop Schedule',
description: 'Disable (stop) a running schedule in Dagster.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
instigationStateId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'InstigationState ID of the schedule to stop — available from dagster_list_schedules output',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => ({
query: STOP_SCHEDULE_MUTATION,
variables: { id: params.instigationStateId },
}),
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ stopRunningSchedule?: unknown }>(response)
const result = data.data?.stopRunningSchedule as ScheduleMutationResult | undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (result.type === 'ScheduleStateResult' && result.scheduleState) {
return {
success: true,
output: {
id: result.scheduleState.id,
status: result.scheduleState.status,
},
}
}
throw new Error(`${result.type}: ${dagsterUnionErrorMessage(result, 'Stop schedule failed')}`)
},
outputs: {
id: {
type: 'string',
description: 'Instigator state ID of the schedule',
},
status: {
type: 'string',
description: 'Updated schedule status (RUNNING or STOPPED)',
},
},
}
+109
View File
@@ -0,0 +1,109 @@
import type { DagsterSensorMutationResponse, DagsterStopSensorParams } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface StopSensorResult {
type: string
instigationState?: {
id: string
status: string
}
message?: string
}
const STOP_SENSOR_MUTATION = `
mutation StopSensor($id: String) {
stopSensor(id: $id) {
type: __typename
... on StopSensorMutationResult {
instigationState {
id
status
}
}
... on UnauthorizedError {
__typename
message
}
... on PythonError {
__typename
message
}
}
}
`
export const stopSensorTool: ToolConfig<DagsterStopSensorParams, DagsterSensorMutationResponse> = {
id: 'dagster_stop_sensor',
name: 'Dagster Stop Sensor',
description: 'Disable (stop) a running sensor in Dagster.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
instigationStateId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description:
'InstigationState ID of the sensor to stop — available from dagster_list_sensors output',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => ({
query: STOP_SENSOR_MUTATION,
variables: { id: params.instigationStateId },
}),
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ stopSensor?: unknown }>(response)
const result = data.data?.stopSensor as StopSensorResult | undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (result.type === 'StopSensorMutationResult' && result.instigationState) {
return {
success: true,
output: {
id: result.instigationState.id,
status: result.instigationState.status,
},
}
}
throw new Error(`${result.type}: ${dagsterUnionErrorMessage(result, 'Stop sensor failed')}`)
},
outputs: {
id: {
type: 'string',
description: 'Instigator state ID of the sensor',
},
status: {
type: 'string',
description: 'Updated sensor status (RUNNING or STOPPED)',
},
},
}
+127
View File
@@ -0,0 +1,127 @@
import type { DagsterTerminateRunParams, DagsterTerminateRunResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
/** Fields returned from `terminateRun` for all union members. */
interface DagsterTerminateRunPayload {
__typename?: string
run?: { runId: string }
message?: string
}
const TERMINATE_RUN_MUTATION = `
mutation TerminateRun($runId: String!) {
terminateRun(runId: $runId) {
__typename
... on TerminateRunSuccess {
run {
runId
}
}
... on TerminateRunFailure {
run {
runId
}
message
}
... on RunNotFoundError {
message
}
... on UnauthorizedError {
message
}
... on PythonError {
message
}
}
}
`
export const terminateRunTool: ToolConfig<DagsterTerminateRunParams, DagsterTerminateRunResponse> =
{
id: 'dagster_terminate_run',
name: 'Dagster Terminate Run',
description: 'Terminate an in-progress Dagster run.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
runId: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'The ID of the run to terminate',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => ({
query: TERMINATE_RUN_MUTATION,
variables: { runId: params.runId },
}),
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ terminateRun?: DagsterTerminateRunPayload }>(
response
)
const result = data.data?.terminateRun
if (!result) throw new Error('Unexpected response from Dagster')
if (result.__typename === 'TerminateRunSuccess' && result.run?.runId) {
return {
success: true,
output: {
success: true,
runId: result.run.runId,
message: null,
},
}
}
if (result.__typename === 'TerminateRunFailure') {
throw new Error(
`TerminateRunFailure: ${dagsterUnionErrorMessage(result, 'Terminate run failed')}`
)
}
throw new Error(dagsterUnionErrorMessage(result, 'Terminate run failed'))
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the run was successfully terminated',
},
runId: {
type: 'string',
description: 'The ID of the terminated run',
},
message: {
type: 'string',
description: 'Error or status message if termination failed',
optional: true,
},
},
}
+296
View File
@@ -0,0 +1,296 @@
import type { ToolResponse } from '@/tools/types'
export interface DagsterBaseParams {
host: string
apiKey?: string
}
export interface DagsterLaunchRunParams extends DagsterBaseParams {
repositoryLocationName: string
repositoryName: string
jobName: string
runConfigJson?: string
tags?: string
}
export interface DagsterLaunchRunResponse extends ToolResponse {
output: {
runId: string
}
}
export interface DagsterGetRunParams extends DagsterBaseParams {
runId: string
}
export interface DagsterGetRunResponse extends ToolResponse {
output: {
runId: string
jobName: string | null
status: string
mode: string | null
startTime: number | null
endTime: number | null
creationTime: number | null
updateTime: number | null
parentRunId: string | null
rootRunId: string | null
canTerminate: boolean
assetSelection: string[] | null
runConfigYaml: string | null
tags: Array<{ key: string; value: string }> | null
}
}
export interface DagsterListRunsParams extends DagsterBaseParams {
jobName?: string
statuses?: string
createdAfter?: number
createdBefore?: number
cursor?: string
limit?: number
}
export interface DagsterListRunsResponse extends ToolResponse {
output: {
runs: Array<{
runId: string
jobName: string | null
status: string
tags: Array<{ key: string; value: string }> | null
startTime: number | null
endTime: number | null
}>
cursor: string | null
hasMore: boolean
}
}
export interface DagsterListJobsResponse extends ToolResponse {
output: {
jobs: Array<{
name: string
repositoryName: string
}>
}
}
export interface DagsterTerminateRunParams extends DagsterBaseParams {
runId: string
}
export interface DagsterTerminateRunResponse extends ToolResponse {
output: {
success: boolean
runId: string
message: string | null
}
}
export interface DagsterGetRunLogsParams extends DagsterBaseParams {
runId: string
afterCursor?: string
limit?: number
}
export interface DagsterGetRunLogsResponse extends ToolResponse {
output: {
events: Array<{
type: string
message: string
timestamp: string
level: string
stepKey: string | null
eventType: string | null
}>
cursor: string | null
hasMore: boolean
}
}
export interface DagsterReexecuteRunParams extends DagsterBaseParams {
parentRunId: string
strategy: string
}
export interface DagsterReexecuteRunResponse extends ToolResponse {
output: {
runId: string
}
}
export interface DagsterDeleteRunParams extends DagsterBaseParams {
runId: string
}
export interface DagsterDeleteRunResponse extends ToolResponse {
output: {
runId: string
}
}
export interface DagsterListSchedulesParams extends DagsterBaseParams {
repositoryLocationName: string
repositoryName: string
scheduleStatus?: string
}
export interface DagsterListSchedulesResponse extends ToolResponse {
output: {
schedules: Array<{
name: string
cronSchedule: string | null
jobName: string | null
status: string
id: string | null
description: string | null
executionTimezone: string | null
}>
}
}
export interface DagsterStartScheduleParams extends DagsterBaseParams {
repositoryLocationName: string
repositoryName: string
scheduleName: string
}
export interface DagsterScheduleMutationResponse extends ToolResponse {
output: {
id: string
status: string
}
}
export interface DagsterStopScheduleParams extends DagsterBaseParams {
instigationStateId: string
}
export interface DagsterListSensorsParams extends DagsterBaseParams {
repositoryLocationName: string
repositoryName: string
sensorStatus?: string
}
export interface DagsterListSensorsResponse extends ToolResponse {
output: {
sensors: Array<{
name: string
sensorType: string | null
status: string
id: string | null
description: string | null
}>
}
}
export interface DagsterStartSensorParams extends DagsterBaseParams {
repositoryLocationName: string
repositoryName: string
sensorName: string
}
export interface DagsterSensorMutationResponse extends ToolResponse {
output: {
id: string
status: string
}
}
export interface DagsterStopSensorParams extends DagsterBaseParams {
instigationStateId: string
}
export interface DagsterListAssetsParams extends DagsterBaseParams {
prefix?: string
cursor?: string
limit?: number
}
export interface DagsterListAssetsResponse extends ToolResponse {
output: {
assets: Array<{ assetKey: string; path: string[] }>
cursor: string | null
hasMore: boolean
}
}
export interface DagsterGetAssetParams extends DagsterBaseParams {
assetKey: string
}
export interface DagsterGetAssetResponse extends ToolResponse {
output: {
assetKey: string
path: string[]
groupName: string | null
description: string | null
jobNames: string[] | null
computeKind: string | null
isPartitioned: boolean | null
latestMaterialization: {
runId: string
timestamp: string
partition: string | null
stepKey: string | null
} | null
}
}
export interface DagsterMaterializeAssetsParams extends DagsterBaseParams {
repositoryLocationName: string
repositoryName: string
jobName: string
assetSelection: string
tags?: string
}
export interface DagsterMaterializeAssetsResponse extends ToolResponse {
output: {
runId: string
}
}
export interface DagsterReportAssetMaterializationParams extends DagsterBaseParams {
assetKey: string
eventType?: string
partitionKeys?: string
description?: string
}
export interface DagsterReportAssetMaterializationResponse extends ToolResponse {
output: {
success: boolean
assetKey: string
}
}
export interface DagsterWipeAssetParams extends DagsterBaseParams {
assetKey: string
}
export interface DagsterWipeAssetResponse extends ToolResponse {
output: {
success: boolean
assetKey: string
}
}
export type DagsterResponse =
| DagsterLaunchRunResponse
| DagsterGetRunResponse
| DagsterListRunsResponse
| DagsterListJobsResponse
| DagsterTerminateRunResponse
| DagsterGetRunLogsResponse
| DagsterReexecuteRunResponse
| DagsterDeleteRunResponse
| DagsterListSchedulesResponse
| DagsterScheduleMutationResponse
| DagsterListSensorsResponse
| DagsterSensorMutationResponse
| DagsterListAssetsResponse
| DagsterGetAssetResponse
| DagsterMaterializeAssetsResponse
| DagsterReportAssetMaterializationResponse
| DagsterWipeAssetResponse
+82
View File
@@ -0,0 +1,82 @@
/**
* Builds the GraphQL endpoint URL from a Dagster host, tolerating surrounding whitespace and a
* trailing slash (e.g. `https://myorg.dagster.cloud/prod` → `https://myorg.dagster.cloud/prod/graphql`).
*/
export function dagsterGraphqlUrl(host: string): string {
return `${host.trim().replace(/\/$/, '')}/graphql`
}
/**
* Builds the request headers for a Dagster GraphQL call, attaching the Dagster+ API token when one
* is provided (omitted for OSS / self-hosted instances).
*/
export function dagsterRequestHeaders(params: { apiKey?: string }): Record<string, string> {
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
if (params.apiKey) headers['Dagster-Cloud-Api-Token'] = params.apiKey.trim()
return headers
}
/**
* Splits a slash-delimited asset key string into a Dagster asset key path
* (e.g. `prefix/my_asset` → `['prefix', 'my_asset']`).
*/
export function parseAssetKeyPath(input: string): string[] {
return input
.split('/')
.map((segment) => segment.trim())
.filter(Boolean)
}
/**
* Parses a comma- or newline-separated list of slash-delimited asset keys into the
* `[AssetKeyInput!]` shape expected by Dagster (`{ path: string[] }[]`).
*/
export function parseAssetSelection(input: string): Array<{ path: string[] }> {
return input
.split(/[\n,]/)
.map((key) => key.trim())
.filter(Boolean)
.map((key) => ({ path: parseAssetKeyPath(key) }))
}
/**
* Parses a Dagster GraphQL JSON body and throws if the HTTP status is not OK or the payload
* contains top-level GraphQL errors.
*
* Field errors should be requested with `... on Error { __typename message }` (or at least
* `message`) so union failures are not returned as empty objects.
*/
export async function parseDagsterGraphqlResponse<TData extends Record<string, unknown>>(
response: Response
): Promise<{ data?: TData }> {
let payload: {
data?: TData
errors?: ReadonlyArray<{ message?: string }>
}
try {
payload = (await response.json()) as {
data?: TData
errors?: ReadonlyArray<{ message?: string }>
}
} catch {
throw new Error('Invalid JSON response from Dagster')
}
if (!response.ok) {
throw new Error(payload.errors?.[0]?.message || 'Dagster GraphQL request failed')
}
if (payload.errors?.length) {
throw new Error(payload.errors[0]?.message ?? 'Dagster GraphQL request failed')
}
return { data: payload.data }
}
/**
* Message from a field that includes `... on Error { message }`, or a fallback when the
* payload is not a GraphQL `Error` type with a string message.
*/
export function dagsterUnionErrorMessage(
result: { message?: string } | undefined,
fallback: string
): string {
return typeof result?.message === 'string' ? result.message : fallback
}
+115
View File
@@ -0,0 +1,115 @@
import type { DagsterWipeAssetParams, DagsterWipeAssetResponse } from '@/tools/dagster/types'
import {
dagsterGraphqlUrl,
dagsterRequestHeaders,
dagsterUnionErrorMessage,
parseAssetKeyPath,
parseDagsterGraphqlResponse,
} from '@/tools/dagster/utils'
import type { ToolConfig } from '@/tools/types'
interface WipeAssetResult {
type: string
assetPartitionRanges?: Array<{ assetKey: { path: string[] } }>
message?: string
}
const WIPE_ASSET_MUTATION = `
mutation WipeAsset($assetPartitionRanges: [PartitionsByAssetSelector!]!) {
wipeAssets(assetPartitionRanges: $assetPartitionRanges) {
type: __typename
... on AssetWipeSuccess {
assetPartitionRanges {
assetKey {
path
}
}
}
... on AssetNotFoundError {
message
}
... on UnauthorizedError {
message
}
... on UnsupportedOperationError {
message
}
... on PythonError {
message
}
}
}
`
export const wipeAssetTool: ToolConfig<DagsterWipeAssetParams, DagsterWipeAssetResponse> = {
id: 'dagster_wipe_asset',
name: 'Dagster Wipe Asset',
description:
'DESTRUCTIVE: permanently wipes ALL materialization history (every partition) for an asset. This cannot be undone.',
version: '1.0.0',
params: {
host: {
type: 'string',
required: true,
visibility: 'user-only',
description:
'Dagster host URL (e.g., https://myorg.dagster.cloud/prod or http://localhost:3001)',
},
apiKey: {
type: 'string',
required: false,
visibility: 'user-only',
description: 'Dagster+ API token (leave blank for OSS / self-hosted)',
},
assetKey: {
type: 'string',
required: true,
visibility: 'user-or-llm',
description: 'Slash-delimited asset key to wipe, e.g. "my_asset" or "raw/events"',
},
},
request: {
url: (params) => dagsterGraphqlUrl(params.host),
method: 'POST',
headers: (params) => dagsterRequestHeaders(params),
body: (params) => ({
query: WIPE_ASSET_MUTATION,
variables: {
assetPartitionRanges: [{ assetKey: { path: parseAssetKeyPath(params.assetKey) } }],
},
}),
},
transformResponse: async (response: Response) => {
const data = await parseDagsterGraphqlResponse<{ wipeAssets?: unknown }>(response)
const result = data.data?.wipeAssets as WipeAssetResult | undefined
if (!result) throw new Error('Unexpected response from Dagster')
if (result.type === 'AssetWipeSuccess') {
const wipedKey = result.assetPartitionRanges?.[0]?.assetKey.path.join('/') ?? ''
return {
success: true,
output: {
success: true,
assetKey: wipedKey,
},
}
}
throw new Error(`${result.type}: ${dagsterUnionErrorMessage(result, 'Wipe asset failed')}`)
},
outputs: {
success: {
type: 'boolean',
description: 'Whether the asset was wiped successfully',
},
assetKey: {
type: 'string',
description: 'Slash-joined asset key that was wiped',
},
},
}