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