diff --git a/.changeset/accumulator-output-types.md b/.changeset/accumulator-output-types.md new file mode 100644 index 0000000..ac3b137 --- /dev/null +++ b/.changeset/accumulator-output-types.md @@ -0,0 +1,5 @@ +--- +"@remoraflow/core": patch +--- + +The validator now types a `for-each` or `while` accumulator from its loop body's output, so an accumulator that builds the wrong shape is reported as an error. It warns separately when the initial value, which the loop outputs if the body never runs, does not match the body's output. Type inference also flattens lists of arrays correctly, types empty literal arrays as arrays with no items, checks each member of a union and each tuple item against the target schema, and treats different JSON Schema types as disjoint. The `for-each` and `while` step descriptions now explain how to collect outputs without an accumulator and how to append to an array accumulator. diff --git a/.changeset/faster-workflow-definition-schema.md b/.changeset/faster-workflow-definition-schema.md new file mode 100644 index 0000000..bb3f356 --- /dev/null +++ b/.changeset/faster-workflow-definition-schema.md @@ -0,0 +1,5 @@ +--- +"@remoraflow/core": patch +--- + +The workflow definition schema is about twice as fast to build, because the union of step types is now built in one call instead of a chain of `.or()` and `.exclude()` calls. diff --git a/.changeset/switch-case-forwards-branch-output.md b/.changeset/switch-case-forwards-branch-output.md new file mode 100644 index 0000000..bb13659 --- /dev/null +++ b/.changeset/switch-case-forwards-branch-output.md @@ -0,0 +1,5 @@ +--- +"@remoraflow/core": minor +--- + +A `switch-case` step's output is now the value returned by its selected branch, instead of `null`. The validator types it as the union of the branch outputs. Field access on a union type no longer warns when every member of the union has the field. diff --git a/.changeset/validate-jmespath-function-names.md b/.changeset/validate-jmespath-function-names.md new file mode 100644 index 0000000..51382d9 --- /dev/null +++ b/.changeset/validate-jmespath-function-names.md @@ -0,0 +1,6 @@ +--- +"@remoraflow/core": patch +"@remoraflow/ui": patch +--- + +The validator now reports calls to JMESPath functions that do not exist, such as `append()`, instead of letting them fail at runtime. The list of JMESPath functions is exported as `JMESPATH_FUNCTION_NAMES`, and the UI's syntax highlighting uses it. diff --git a/.changeset/workflow-editor-tools.md b/.changeset/workflow-editor-tools.md new file mode 100644 index 0000000..4bafe6d --- /dev/null +++ b/.changeset/workflow-editor-tools.md @@ -0,0 +1,5 @@ +--- +"@remoraflow/core": minor +--- + +Workflow generation now edits a draft workflow in place instead of resubmitting the full definition on every attempt. The agent uses `write-workflow`, `read-workflow` (JMESPath), `edit-workflow` (JSON Patch), and `submit-workflow`. Write and edit return validation diagnostics without failing the tool call, edits also return the changed workflow, and both accept `submitIfValid` to submit a workflow that has no errors or warnings without a separate call. An edit that uses `replace` on a field that does not exist adds the field. When a required output schema is given and the workflow declares no `outputSchema`, the required schema is used. Tool calls whose top-level object or array arguments were sent as JSON strings are repaired. The generation instructions are marked as an Anthropic prompt-cache breakpoint. `step-end` diagnostic events now include each step's tool calls, with the input and error of failed calls. diff --git a/bun.lock b/bun.lock index 93a2b4d..80cbec9 100644 --- a/bun.lock +++ b/bun.lock @@ -78,13 +78,16 @@ "arktype": "^2.1.29", "capture-console": "^1.0.2", "dedent": "^1.7.2", + "fast-json-patch": "^3.1.1", "jmespath": "^0.16.0", "tokenx": "^1.3.0", "zod": "^4.4.3", }, "devDependencies": { - "@ai-sdk/openai": "^4.0.43", + "@ai-sdk/amazon-bedrock": "5.0.58", + "@ai-sdk/openai-compatible": "3.0.31", "@ai-sdk/provider-utils": "5.0.27", + "@aws-sdk/credential-providers": "^3.1139.0", "@aws/durable-execution-sdk-js": "^1.1.0", "@aws/durable-execution-sdk-js-testing": "^1.1.1", "@temporalio/client": "^1.23.0", @@ -161,10 +164,16 @@ "packages": { "@11ty/gray-matter": ["@11ty/gray-matter@2.1.0", "", { "dependencies": { "js-yaml": "^4.2.0", "section-matter": "^1.0.0" } }, "sha512-fNdBOb3MgDz/1UCIoeY4wDuGbp+/3s5y6UrsyfMabECbh/CVycj7Er33JXqSxRwxrRKXcGE1Jipuq/1mODInsQ=="], + "@ai-sdk/amazon-bedrock": ["@ai-sdk/amazon-bedrock@5.0.58", "", { "dependencies": { "@ai-sdk/anthropic": "4.0.39", "@ai-sdk/openai": "4.0.43", "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27", "@smithy/eventstream-codec": "^4.3.3", "@smithy/util-utf8": "^4.3.3", "aws4fetch": "^1.0.20" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-p5skfe1p/Ulz0O+Qu2ZubcrSA5h0aYqicd4AQHza66GcvExVWRcn3isacwGYs4FT+5fHPS3IC1zneqy6goOHLA=="], + + "@ai-sdk/anthropic": ["@ai-sdk/anthropic@4.0.39", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-JAMGtYeEuaBzqbsPO4fkho6vQyNoVhsHASM4o59wmJRU6Vh7prjOp490Kmc7YQTY+ioU1/xYzXvWOtxZBup0Xw=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@4.0.54", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-x4fAXDqCtYzB/M5vsIQLYcyrzpJuaRgcIwDSw+lpTMMbgH19fU3ds75GSlHNLzfx6Z5yL4Z9+EMr0GJcqVy9QA=="], "@ai-sdk/openai": ["@ai-sdk/openai@4.0.43", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-O5t21AmcBwsEg/l8ro91CHzdBjEkxtJRFh6DH9KDjRrWDeTMuqJt7XkaFHIqL8H0nvjFv8H7kck+JwirUWPS2g=="], + "@ai-sdk/openai-compatible": ["@ai-sdk/openai-compatible@3.0.31", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@ai-sdk/provider-utils": "5.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-g6kbWJzI1mfHoZp2vXPyb/fiMrK8pNRU2dbum8rBHxemx+ryBJWFAwTndBaVefm+gx9A6fK7J/hSrdfRdVJlQQ=="], + "@ai-sdk/provider": ["@ai-sdk/provider@4.0.7", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-6or44XprPzKbr8zkmzosowSE0pxkvJcoojBL+mCZvPUt3kvXp3XSNqeVun9golb1acEfSo6yaEBRT18h2VU+1Q=="], "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@5.0.27", "", { "dependencies": { "@ai-sdk/provider": "4.0.7", "@standard-schema/spec": "^1.1.0", "@workflow/serde": "4.1.0", "eventsource-parser": "^3.0.8", "undici": "^7.28.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-EzAn4pdgG5g0xXtH6lE2zyNmfjDQIDjATkfqzuidEI35g++hh4+07vnjzkT/RmGmIClPZiRj/Q2GMPV2V7mkHw=="], @@ -227,23 +236,27 @@ "@aws-sdk/client-lambda": ["@aws-sdk/client-lambda@3.1012.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.21", "@aws-sdk/credential-provider-node": "^3.972.22", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.22", "@aws-sdk/region-config-resolver": "^3.972.8", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.8", "@smithy/config-resolver": "^4.4.11", "@smithy/core": "^3.23.12", "@smithy/eventstream-serde-browser": "^4.2.12", "@smithy/eventstream-serde-config-resolver": "^4.3.12", "@smithy/eventstream-serde-node": "^4.2.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.26", "@smithy/middleware-retry": "^4.4.43", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.42", "@smithy/util-defaults-mode-node": "^4.2.45", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/util-waiter": "^4.2.13", "tslib": "^2.6.2" } }, "sha512-ngJAVKHaM4zkzIrbnitVuSoPegBEdb8zHD5dmc452sRngoQXNpHIj6tEEZwdAuoVlP3WM9nAXn/monSY2l3pyg=="], - "@aws-sdk/core": ["@aws-sdk/core@3.973.21", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.12", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-OTUcDX9Yfz/FLKbHjiMaP9D4Hs44lYJzN7zBcrK2nDmBt0Wr8D6nYt12QoBkZsW0nVMFsTIGaZCrsU9zCcIMXQ=="], + "@aws-sdk/core": ["@aws-sdk/core@3.978.1", "", { "dependencies": { "@aws-sdk/types": "^3.974.6", "@aws-sdk/xml-builder": "^3.972.41", "@aws/lambda-invoke-store": "^0.3.0", "@smithy/core": "^3.35.0", "@smithy/signature-v4": "^5.7.3", "@smithy/types": "^4.19.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-LbY9aGsEiznDWmUc30Nwv3aIX/+dbwTx8KfS0yOC3NPYMO+O91e6jkT1azf34FwjOndq8/Q+RcVVZz5xnerwdg=="], - "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-33NpkQtmnsjLr9QdZvL3w8bjy+WoBJ+jY8JwuzxIq38rDNi1kwpBWW7Yjh+8bMlksd+ZAWW0fH4S/6OeoAdU5A=="], + "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.972.71", "", { "dependencies": { "@aws-sdk/nested-clients": "^3.997.46", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-rsxsi8tF3vMGdWdaHQNXceHBdb49442yR2Pe08K+Phoc/yICbptdsHwL25Wf0z5+hjhSS5a+hDJHP7Xul4zvHQ=="], - "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-xFke7yjbON4unNOG0TApQwz+o1LH5VhVLgWlUuiLRWNDyBfeHIFje2ck8qHybvJ8Fkm5m3SsN+pvHtVo6PGWlQ=="], + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.72", "", { "dependencies": { "@aws-sdk/core": "^3.978.1", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-xTKO/FWJPozTIXbozVnVGoNBhaGba8TBcx+KyUjRVeOlXE+dUc7GTR1cLvu0uTdIdmemzaFbqqCshXeZA1fZew=="], - "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/credential-provider-env": "^3.972.19", "@aws-sdk/credential-provider-http": "^3.972.21", "@aws-sdk/credential-provider-login": "^3.972.21", "@aws-sdk/credential-provider-process": "^3.972.19", "@aws-sdk/credential-provider-sso": "^3.972.21", "@aws-sdk/credential-provider-web-identity": "^3.972.21", "@aws-sdk/nested-clients": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fmJN7KhB7CoG65w9fC2LVOd2wZbR2d1yJIpZNe2J5CeDPu7nUHSmavuJAeGEoE3OL5UIBVPNhmK/fV/NQrs3Hw=="], + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.74", "", { "dependencies": { "@aws-sdk/core": "^3.978.1", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/fetch-http-handler": "^5.8.0", "@smithy/node-http-handler": "^4.12.1", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-u91E/hT8f4d1xy0Jl7VG4nVKJ3lxbrZkoBTeSVoJdWBiSEUMwMS/9+e0H/aJVQV//Lt5wuzP+E69v4aRSsNTmw=="], - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/nested-clients": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ENU+YCiuQocQjfIf9bPxZ+ZY0wIBkl3SMH22optBQwy8UFpSfonHynXzGT27xQxer4cYTNOpwDqbfo57BusbpQ=="], + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.973.17", "", { "dependencies": { "@aws-sdk/core": "^3.978.1", "@aws-sdk/credential-provider-env": "^3.972.72", "@aws-sdk/credential-provider-http": "^3.972.74", "@aws-sdk/credential-provider-login": "^3.972.79", "@aws-sdk/credential-provider-process": "^3.972.72", "@aws-sdk/credential-provider-sso": "^3.973.16", "@aws-sdk/credential-provider-web-identity": "^3.972.78", "@aws-sdk/nested-clients": "^3.997.46", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/credential-provider-imds": "^4.5.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-ged4KXdBkvIC81bLvNHHuQKdKak/VXhQTR1NWYTTqW0474nlmsxy9O/vlgTIohDDWH3xpBdtVMZRyjb+DnocDA=="], - "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.22", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.19", "@aws-sdk/credential-provider-http": "^3.972.21", "@aws-sdk/credential-provider-ini": "^3.972.21", "@aws-sdk/credential-provider-process": "^3.972.19", "@aws-sdk/credential-provider-sso": "^3.972.21", "@aws-sdk/credential-provider-web-identity": "^3.972.21", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VE6i8nkmrRyhKut7nnfCWRbdDf+CfyRr8ixSwdaPDguYlgvkAO2pHu9oK11XzbSuatB0io1ozI/vpYhelXn8Pg=="], + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.79", "", { "dependencies": { "@aws-sdk/core": "^3.978.1", "@aws-sdk/nested-clients": "^3.997.46", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-L+Z85anONJd8MaiuraO4wRxATCdEejBZ3K3eymzWI5JPXa9sOS9CkIm72PBKqXKX+Z9p9NGMX5AIMXm0LEflgw=="], - "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-hjj5bFo4kf5/WzAMjDEFByVOMbq5gZiagIpJexf7Kp9nIDaGzhCphMsx03NCA8s9zUJzHlD1lXazd7MS+e03Lg=="], + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.84", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.72", "@aws-sdk/credential-provider-http": "^3.972.74", "@aws-sdk/credential-provider-ini": "^3.973.17", "@aws-sdk/credential-provider-process": "^3.972.72", "@aws-sdk/credential-provider-sso": "^3.973.16", "@aws-sdk/credential-provider-web-identity": "^3.972.78", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/credential-provider-imds": "^4.5.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-oHt854odINVwzwsh+c5x69j0ajm4DbqqqVJ+O1ECsCIZeMDAbzFpXItaqP7UZstJj/ATdTk/KFSH0LaNAgV+kA=="], - "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/nested-clients": "^3.996.11", "@aws-sdk/token-providers": "3.1012.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-9jWRCuMZpZKlqCZ46bvievqdfswsyB2yPAr9rOiN+FxaGgf8jrR5iYDqJgscvk1jrbAxiK4cIjHv3XjIAWAhzQ=="], + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.72", "", { "dependencies": { "@aws-sdk/core": "^3.978.1", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-rLIp2xbMjX/k9/od7APpqq1ZgXXnV0pOL1Th3ZsL8Wu0TRtBsDTVS8iPqcfRFcHakFxPvR04OSTv2ka2qOb/2A=="], - "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/nested-clients": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ShWQO/cQVZ+j3zUDK7Kj+m7grPzQCVA2iaZdJ+hJTGvVH5lR32Ip/rgZZ+zBdH6D6wczP9Upa4NMXoqJdGpK1g=="], + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.973.16", "", { "dependencies": { "@aws-sdk/core": "^3.978.1", "@aws-sdk/nested-clients": "^3.997.46", "@aws-sdk/token-providers": "3.1138.0", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-IGihaJfFZYacJJr/odqILCoK7W/mvrZ7cuK7ECn3sAu4vLC6u0V8bS7mCGbdugJ8Aum2tnvqmx0F2MRFp2rn9g=="], + + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.78", "", { "dependencies": { "@aws-sdk/core": "^3.978.1", "@aws-sdk/nested-clients": "^3.997.46", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-/y9WvNtlcPBGLR0qc1a+9J/xtYZfVczvLUOuXaVWylzttH7ewsxwHtjmiJSolNrVSDorIxHGHMU61CbonRkmwA=="], + + "@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.1139.0", "", { "dependencies": { "@aws-sdk/core": "^3.978.1", "@aws-sdk/credential-provider-cognito-identity": "^3.972.71", "@aws-sdk/credential-provider-env": "^3.972.72", "@aws-sdk/credential-provider-http": "^3.972.74", "@aws-sdk/credential-provider-ini": "^3.973.17", "@aws-sdk/credential-provider-login": "^3.972.79", "@aws-sdk/credential-provider-node": "^3.972.84", "@aws-sdk/credential-provider-process": "^3.972.72", "@aws-sdk/credential-provider-sso": "^3.973.16", "@aws-sdk/credential-provider-web-identity": "^3.972.78", "@aws-sdk/nested-clients": "^3.997.46", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/credential-provider-imds": "^4.5.2", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-vOLaAqhfk6v1WWgBdr5AgnPb4nvuifUzrqFaq6j5SLiGSJxYInS/1Qb6E+kFmKummdSvf7uzytGpgurQVvczdQ=="], "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wAr2REfKsqoKQ+OkNqvOShnBoh+nkPurDKW7uAeVSu6kUECnWlSJiPvnoqxGlfousEY/v9LfS9sNc46hjSYDIQ=="], @@ -253,13 +266,15 @@ "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.22", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-retry": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-pZPNGWZVQvgUIO/P9PXZNz7ciq9mLYb/wQEurg3phKTa3DiBIunIRcgA0eBNwmog6S3oy0KR1bv4EJ4ld9A5sQ=="], - "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.11", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.21", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.22", "@aws-sdk/region-config-resolver": "^3.972.8", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.8", "@smithy/config-resolver": "^4.4.11", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.26", "@smithy/middleware-retry": "^4.4.43", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.42", "@smithy/util-defaults-mode-node": "^4.2.45", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-i7SwoSR4JB/79JoGDUACnFUQOZwXGLWNX35lIb1Pq72nUGlVV+RFZp+BLa8S+mog2pbXU9+6Kc5YwGiMi5bKhQ=="], + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.997.46", "", { "dependencies": { "@aws-sdk/core": "^3.978.1", "@aws-sdk/signature-v4-multi-region": "^3.996.47", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/fetch-http-handler": "^5.8.0", "@smithy/node-http-handler": "^4.12.1", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-oRxtBcka/JGHGs9l9p9IVajGoTP8vTPmoAzdHGy4Qcy9P5vPnDf6nhIeM/COQNY9k/OahImTRaLkHftoXvfcmQ=="], "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.8", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/config-resolver": "^4.4.11", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1eD4uhTDeambO/PNIDVG19A6+v4NdD7xzwLHDutHsUqz0B+i661MwQB2eYO4/crcCvCiQG4SRm1k81k54FEIvw=="], - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1012.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/nested-clients": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-vzKwy020zjuiF4WTJzejx5nYcXJnRhHpb6i3lyZHIwfFwXG1yX4bzBVNMWYWF+bz1i2Pp2VhJbPyzpqj4VuJXQ=="], + "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.996.47", "", { "dependencies": { "@aws-sdk/types": "^3.974.6", "@smithy/signature-v4": "^5.7.3", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-Zk08macMvQTHzQJCLJVkOlviVoqwYMrpXv4lmLN7b7sAbiMoOK7Go0NYdR5UeF+MW8LIbRmwrNy9u/5VvX1U5g=="], + + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1138.0", "", { "dependencies": { "@aws-sdk/core": "^3.978.1", "@aws-sdk/nested-clients": "^3.997.46", "@aws-sdk/types": "^3.974.6", "@smithy/core": "^3.35.0", "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-GpyAr0DD63YOEmYFM6Df+gJuIgC92MMTiBK4FTKfxii5MJ9ge20epR7LyroulscYlG89J+ZB2ivFDPjvfQhzdw=="], - "@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + "@aws-sdk/types": ["@aws-sdk/types@3.974.6", "", { "dependencies": { "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-v/clNZzZnDxGyvpHMOGpJKVXFAExJzUNAAjaWGdcx8QAcXLGwTaOkw33p5SHAi0YAioK32xB3hWwOekRVfmfKg=="], "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.996.5", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-endpoints": "^3.3.3", "tslib": "^2.6.2" } }, "sha512-Uh93L5sXFNbyR5sEPMzUU8tJ++Ku97EY4udmC01nB8Zu+xfBPwpIwJ6F7snqQeq8h2pf+8SGN5/NoytfKgYPIw=="], @@ -269,13 +284,13 @@ "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.973.8", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.22", "@aws-sdk/types": "^3.973.6", "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-Kvb96TafGPLYo4Z2GRCzQTne77epXgiZEo0DDXwavzkWmgDV/1XD1tMA766gzRcHHFUraWsE+4T8DKtPTZUxgQ=="], - "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.13", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.6", "tslib": "^2.6.2" } }, "sha512-I/+BMxM4WE/6xL0tyV7tAUDOAXmyw/va1oGr/eSly43HmLUcD1G+v96vEKAA8VoLcZ03ZQo/PWzjmN9zQErqPQ=="], + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.41", "", { "dependencies": { "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-ctjVSyCMegrWfXlx6VqzSBFI6UqmQ5ZlnfMhdLIiWmhoH8UAQxSCP5N3OpG7X3k4LnS7ou74C4mt20+bfTW2aQ=="], "@aws/durable-execution-sdk-js": ["@aws/durable-execution-sdk-js@1.1.0", "", { "dependencies": { "@aws-sdk/client-lambda": "^3.943.0" } }, "sha512-pzYD8g7rMohqK+Tia4MB6oFZrO9rWPgs2wGRvH/bG4GebzKrSmkL3MLYDATs1yO0yCXZjjqoFVFFWkyNqv6JYw=="], "@aws/durable-execution-sdk-js-testing": ["@aws/durable-execution-sdk-js-testing@1.1.1", "", { "dependencies": { "@aws-sdk/client-lambda": "^3.943.0", "@sinonjs/fake-timers": "^13.0.5", "commander": "^14.0.2" }, "peerDependencies": { "@aws/durable-execution-sdk-js": "^1.0.1" }, "bin": { "run-durable": "dist/cli/run-durable.mjs" } }, "sha512-4zjEDKBKmKl2Nu9FFF+RgnmOp2yKuqHegxRfGQXI+sinCQsz6FX3ESlzAFVOWXkt0KgdhoQvwVTWRrXQVFgegA=="], - "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], + "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.3.0", "", {}, "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ=="], "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], @@ -949,11 +964,11 @@ "@smithy/config-resolver": ["@smithy/config-resolver@4.4.11", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-YxFiiG4YDAtX7WMN7RuhHZLeTmRRAOyCbr+zB8e3AQzHPnUhS8zXjB1+cniPVQI3xbWsQPM0X2aaIkO/ME0ymw=="], - "@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + "@smithy/core": ["@smithy/core@3.35.0", "", { "dependencies": { "@smithy/types": "^4.19.0", "tslib": "^2.6.2" } }, "sha512-zRMhfkByhT2snNdr1si24vJitU6Cr9ix2MikUfWmkAgp4jrNP0GcKSP5YvwQ+TlI8AZXER5QOGJn3JsVtSD9/A=="], - "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="], + "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-A9uSdn72ozbRUSit0eib0TW7nXuNPlaeM0zcGkJ+nE6tFcSDbnmtwoxbTCFBukVQcszDAyvsd7+rTduPTXpygg=="], - "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="], + "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "tslib": "^2.6.2" } }, "sha512-u4EAkaviMcQYlorFSnxFUgF/Dnbgiu2TzGmqOU4h3CL4MbvrYyNCXpFkcBwvAEU11EvspKX2zzS2uX7q+43VdQ=="], "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="], @@ -963,7 +978,7 @@ "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="], - "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="], + "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.8.0", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-ycSJu3tFAQ4v04CBB0agqFMVsSQ1iG3yw+SpgxRqKfaURpQD4CZ8Wn0zPMmSnOuTpTh65Vz+EA0rMrw089wvkA=="], "@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="], @@ -983,7 +998,7 @@ "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="], - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="], + "@smithy/node-http-handler": ["@smithy/node-http-handler@4.12.1", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.18.0", "tslib": "^2.6.2" } }, "sha512-ThMkboGeONWXAelq9FvGsuJC4rOi+qyC4/zhUF58xYpxUg5sQKx2VXZYJmtNjr4dSuBJ1HeJXETQILCz3wOHvw=="], "@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], @@ -997,11 +1012,11 @@ "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="], - "@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="], + "@smithy/signature-v4": ["@smithy/signature-v4@5.7.3", "", { "dependencies": { "@smithy/core": "^3.33.3", "@smithy/types": "^4.17.2", "tslib": "^2.6.2" } }, "sha512-7ImGm+FkHRLcBaRttIAMZ6bzJZWb2cJGoYjq46F2UjycujWzrL9GEN9h4w7eQyXJYnltrUhxbbieBAIRrdqpow=="], "@smithy/smithy-client": ["@smithy/smithy-client@4.12.6", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.26", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-aib3f0jiMsJ6+cvDnXipBsGDL7ztknYSVqJs1FdN9P+u9tr/VzOR7iygSh6EUOdaBeMCMSh3N0VdyYsG4o91DQ=="], - "@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + "@smithy/types": ["@smithy/types@4.19.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-r7jh49VJxGerfAcTQA6gXcKc+98zOp/tqRwzYjgOE+iSQsP6cEU1hq2QzbuipmP68QtYdY9wKEhiCQZIzHgZ4Q=="], "@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], @@ -1031,7 +1046,7 @@ "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], - "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + "@smithy/util-utf8": ["@smithy/util-utf8@4.5.2", "", { "dependencies": { "@smithy/core": "^3.33.2", "tslib": "^2.6.2" } }, "sha512-7wNWV7SugHpcMA7uzEawJNpE0GrasXM7a9E+1+Wm6NxVuDClESac/AKt+G7jMZNUz5vBLWqKlqVV7Sv7AtUr6Q=="], "@smithy/util-waiter": ["@smithy/util-waiter@4.2.13", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ=="], @@ -1363,6 +1378,8 @@ "array-union": ["array-union@2.1.0", "", {}, "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw=="], + "aws4fetch": ["aws4fetch@1.0.20", "", {}, "sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g=="], + "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], @@ -1549,6 +1566,8 @@ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + "fast-json-patch": ["fast-json-patch@3.1.1", "", {}, "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ=="], + "fast-uri": ["fast-uri@3.1.3", "", {}, "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg=="], "fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="], @@ -2265,10 +2284,72 @@ "@antfu/install-pkg/package-manager-detector": ["package-manager-detector@1.8.0", "", {}, "sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A=="], + "@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-crypto/sha256-browser/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-crypto/sha256-js/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-crypto/util/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], + "@aws-sdk/client-lambda/@aws-sdk/core": ["@aws-sdk/core@3.973.21", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.12", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-OTUcDX9Yfz/FLKbHjiMaP9D4Hs44lYJzN7zBcrK2nDmBt0Wr8D6nYt12QoBkZsW0nVMFsTIGaZCrsU9zCcIMXQ=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.22", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.19", "@aws-sdk/credential-provider-http": "^3.972.21", "@aws-sdk/credential-provider-ini": "^3.972.21", "@aws-sdk/credential-provider-process": "^3.972.19", "@aws-sdk/credential-provider-sso": "^3.972.21", "@aws-sdk/credential-provider-web-identity": "^3.972.21", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VE6i8nkmrRyhKut7nnfCWRbdDf+CfyRr8ixSwdaPDguYlgvkAO2pHu9oK11XzbSuatB0io1ozI/vpYhelXn8Pg=="], + + "@aws-sdk/client-lambda/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-sdk/client-lambda/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + + "@aws-sdk/client-lambda/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="], + + "@aws-sdk/client-lambda/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="], + + "@aws-sdk/client-lambda/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/client-lambda/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/middleware-host-header/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-sdk/middleware-host-header/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/middleware-logger/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-sdk/middleware-logger/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/middleware-recursion-detection/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-sdk/middleware-recursion-detection/@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], + + "@aws-sdk/middleware-recursion-detection/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/middleware-user-agent/@aws-sdk/core": ["@aws-sdk/core@3.973.21", "", { "dependencies": { "@aws-sdk/types": "^3.973.6", "@aws-sdk/xml-builder": "^3.972.12", "@smithy/core": "^3.23.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/signature-v4": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-OTUcDX9Yfz/FLKbHjiMaP9D4Hs44lYJzN7zBcrK2nDmBt0Wr8D6nYt12QoBkZsW0nVMFsTIGaZCrsU9zCcIMXQ=="], + + "@aws-sdk/middleware-user-agent/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-sdk/middleware-user-agent/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + + "@aws-sdk/middleware-user-agent/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/region-config-resolver/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-sdk/region-config-resolver/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/util-endpoints/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-sdk/util-endpoints/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/util-user-agent-browser/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-sdk/util-user-agent-browser/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-sdk/util-user-agent-node/@aws-sdk/types": ["@aws-sdk/types@3.973.6", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw=="], + + "@aws-sdk/util-user-agent-node/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + "@changesets/parse/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], "@docsearch/react/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], @@ -2441,6 +2522,84 @@ "@remoraflow/ui/ai": ["ai@6.0.116", "", { "dependencies": { "@ai-sdk/gateway": "3.0.66", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.19", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-7yM+cTmyRLeNIXwt4Vj+mrrJgVQ9RMIW5WO0ydoLoYkewIvsMcvUmqS4j2RJTUXaF1HphwmSKUMQ/HypNRGOmA=="], + "@smithy/abort-controller/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/config-resolver/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/eventstream-serde-browser/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/eventstream-serde-config-resolver/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/eventstream-serde-node/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/eventstream-serde-universal/@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="], + + "@smithy/eventstream-serde-universal/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/hash-node/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/hash-node/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/invalid-dependency/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/middleware-content-length/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/middleware-endpoint/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + + "@smithy/middleware-endpoint/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/middleware-retry/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/middleware-serde/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + + "@smithy/middleware-serde/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/middleware-stack/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/node-config-provider/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/property-provider/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/protocol-http/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/querystring-builder/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/querystring-parser/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/service-error-classification/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/shared-ini-file-loader/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/smithy-client/@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], + + "@smithy/smithy-client/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/url-parser/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-base64/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/util-defaults-mode-browser/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-defaults-mode-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="], + + "@smithy/util-defaults-mode-node/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-endpoints/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-middleware/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-retry/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-stream/@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="], + + "@smithy/util-stream/@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="], + + "@smithy/util-stream/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@smithy/util-stream/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/util-waiter/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + "@tailwindcss/node/enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], "@tailwindcss/node/lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], @@ -2533,10 +2692,44 @@ "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + "@aws-crypto/crc32/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-crypto/sha256-browser/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@aws-crypto/sha256-js/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + + "@aws-crypto/util/@aws-sdk/types/@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], + "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], + "@aws-sdk/client-lambda/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.13", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.6", "tslib": "^2.6.2" } }, "sha512-I/+BMxM4WE/6xL0tyV7tAUDOAXmyw/va1oGr/eSly43HmLUcD1G+v96vEKAA8VoLcZ03ZQo/PWzjmN9zQErqPQ=="], + + "@aws-sdk/client-lambda/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-33NpkQtmnsjLr9QdZvL3w8bjy+WoBJ+jY8JwuzxIq38rDNi1kwpBWW7Yjh+8bMlksd+ZAWW0fH4S/6OeoAdU5A=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/types": "^3.973.6", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-xFke7yjbON4unNOG0TApQwz+o1LH5VhVLgWlUuiLRWNDyBfeHIFje2ck8qHybvJ8Fkm5m3SsN+pvHtVo6PGWlQ=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/credential-provider-env": "^3.972.19", "@aws-sdk/credential-provider-http": "^3.972.21", "@aws-sdk/credential-provider-login": "^3.972.21", "@aws-sdk/credential-provider-process": "^3.972.19", "@aws-sdk/credential-provider-sso": "^3.972.21", "@aws-sdk/credential-provider-web-identity": "^3.972.21", "@aws-sdk/nested-clients": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fmJN7KhB7CoG65w9fC2LVOd2wZbR2d1yJIpZNe2J5CeDPu7nUHSmavuJAeGEoE3OL5UIBVPNhmK/fV/NQrs3Hw=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.19", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-hjj5bFo4kf5/WzAMjDEFByVOMbq5gZiagIpJexf7Kp9nIDaGzhCphMsx03NCA8s9zUJzHlD1lXazd7MS+e03Lg=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/nested-clients": "^3.996.11", "@aws-sdk/token-providers": "3.1012.0", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-9jWRCuMZpZKlqCZ46bvievqdfswsyB2yPAr9rOiN+FxaGgf8jrR5iYDqJgscvk1jrbAxiK4cIjHv3XjIAWAhzQ=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/nested-clients": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ShWQO/cQVZ+j3zUDK7Kj+m7grPzQCVA2iaZdJ+hJTGvVH5lR32Ip/rgZZ+zBdH6D6wczP9Upa4NMXoqJdGpK1g=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="], + + "@aws-sdk/middleware-user-agent/@aws-sdk/core/@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.13", "", { "dependencies": { "@smithy/types": "^4.13.1", "fast-xml-parser": "5.5.6", "tslib": "^2.6.2" } }, "sha512-I/+BMxM4WE/6xL0tyV7tAUDOAXmyw/va1oGr/eSly43HmLUcD1G+v96vEKAA8VoLcZ03ZQo/PWzjmN9zQErqPQ=="], + + "@aws-sdk/middleware-user-agent/@aws-sdk/core/@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="], + + "@aws-sdk/middleware-user-agent/@aws-sdk/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@aws-sdk/middleware-user-agent/@smithy/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + "@happy-dom/global-registrator/@types/node/undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], "@inngest/ai/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], @@ -2593,6 +2786,12 @@ "@remoraflow/ui/ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.19", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-3eG55CrSWCu2SXlqq2QCsFjo3+E7+Gmg7i/oRVoSZzIodTuDSfLb3MRje67xE9RFea73Zao7Lm4mADIfUETKGg=="], + "@smithy/middleware-endpoint/@smithy/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/middleware-serde/@smithy/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + + "@smithy/smithy-client/@smithy/core/@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], + "@tailwindcss/node/enhanced-resolve/tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="], "@tailwindcss/node/lightningcss/lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], @@ -2681,6 +2880,16 @@ "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.21", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/nested-clients": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ENU+YCiuQocQjfIf9bPxZ+ZY0wIBkl3SMH22optBQwy8UFpSfonHynXzGT27xQxer4cYTNOpwDqbfo57BusbpQ=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-ini/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.11", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.21", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.22", "@aws-sdk/region-config-resolver": "^3.972.8", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.8", "@smithy/config-resolver": "^4.4.11", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.26", "@smithy/middleware-retry": "^4.4.43", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.42", "@smithy/util-defaults-mode-node": "^4.2.45", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-i7SwoSR4JB/79JoGDUACnFUQOZwXGLWNX35lIb1Pq72nUGlVV+RFZp+BLa8S+mog2pbXU9+6Kc5YwGiMi5bKhQ=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.11", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.21", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.22", "@aws-sdk/region-config-resolver": "^3.972.8", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.8", "@smithy/config-resolver": "^4.4.11", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.26", "@smithy/middleware-retry": "^4.4.43", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.42", "@smithy/util-defaults-mode-node": "^4.2.45", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-i7SwoSR4JB/79JoGDUACnFUQOZwXGLWNX35lIb1Pq72nUGlVV+RFZp+BLa8S+mog2pbXU9+6Kc5YwGiMi5bKhQ=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-sso/@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.1012.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.21", "@aws-sdk/nested-clients": "^3.996.11", "@aws-sdk/types": "^3.973.6", "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-vzKwy020zjuiF4WTJzejx5nYcXJnRhHpb6i3lyZHIwfFwXG1yX4bzBVNMWYWF+bz1i2Pp2VhJbPyzpqj4VuJXQ=="], + + "@aws-sdk/client-lambda/@aws-sdk/credential-provider-node/@aws-sdk/credential-provider-web-identity/@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.996.11", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.21", "@aws-sdk/middleware-host-header": "^3.972.8", "@aws-sdk/middleware-logger": "^3.972.8", "@aws-sdk/middleware-recursion-detection": "^3.972.8", "@aws-sdk/middleware-user-agent": "^3.972.22", "@aws-sdk/region-config-resolver": "^3.972.8", "@aws-sdk/types": "^3.973.6", "@aws-sdk/util-endpoints": "^3.996.5", "@aws-sdk/util-user-agent-browser": "^3.972.8", "@aws-sdk/util-user-agent-node": "^3.973.8", "@smithy/config-resolver": "^4.4.11", "@smithy/core": "^3.23.12", "@smithy/fetch-http-handler": "^5.3.15", "@smithy/hash-node": "^4.2.12", "@smithy/invalid-dependency": "^4.2.12", "@smithy/middleware-content-length": "^4.2.12", "@smithy/middleware-endpoint": "^4.4.26", "@smithy/middleware-retry": "^4.4.43", "@smithy/middleware-serde": "^4.2.15", "@smithy/middleware-stack": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/node-http-handler": "^4.5.0", "@smithy/protocol-http": "^5.3.12", "@smithy/smithy-client": "^4.12.6", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-body-length-node": "^4.2.3", "@smithy/util-defaults-mode-browser": "^4.3.42", "@smithy/util-defaults-mode-node": "^4.2.45", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-i7SwoSR4JB/79JoGDUACnFUQOZwXGLWNX35lIb1Pq72nUGlVV+RFZp+BLa8S+mog2pbXU9+6Kc5YwGiMi5bKhQ=="], + "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/protobufjs/@protobufjs/codegen": ["@protobufjs/codegen@2.0.4", "", {}, "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="], "@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/protobufjs/@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.0", "", {}, "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="], diff --git a/packages/core/package.json b/packages/core/package.json index bcf3df1..79e1e0b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -44,6 +44,7 @@ "arktype": "^2.1.29", "capture-console": "^1.0.2", "dedent": "^1.7.2", + "fast-json-patch": "^3.1.1", "jmespath": "^0.16.0", "tokenx": "^1.3.0", "zod": "^4.4.3" @@ -61,8 +62,10 @@ } }, "devDependencies": { - "@ai-sdk/openai": "^4.0.43", + "@ai-sdk/amazon-bedrock": "5.0.58", + "@ai-sdk/openai-compatible": "3.0.31", "@ai-sdk/provider-utils": "5.0.27", + "@aws-sdk/credential-providers": "^3.1139.0", "@aws/durable-execution-sdk-js": "^1.1.0", "@aws/durable-execution-sdk-js-testing": "^1.1.1", "@temporalio/client": "^1.23.0", diff --git a/packages/core/src/execution/step-executors/index.test.ts b/packages/core/src/execution/step-executors/index.test.ts index fdf512e..e89bd7d 100644 --- a/packages/core/src/execution/step-executors/index.test.ts +++ b/packages/core/src/execution/step-executors/index.test.ts @@ -218,6 +218,15 @@ describe("step executors", () => { expect(result.scope?.fallback).toBe("D"); }); + test("outputs the selected branch's return value", async () => { + const result = await runStep( + switchStep, + { x: "a" }, + { workflowDefinition: workflow(switchStep, matched, fallback) }, + ); + expect(result.scope?.sc).toBe("A"); + }); + test("errors with UNRECOGNIZED_CASE when nothing matches", async () => { const noDefault = step("sc", { type: "switch-case", diff --git a/packages/core/src/execution/step-executors/switch-case.ts b/packages/core/src/execution/step-executors/switch-case.ts index 549ffb6..1485150 100644 --- a/packages/core/src/execution/step-executors/switch-case.ts +++ b/packages/core/src/execution/step-executors/switch-case.ts @@ -74,7 +74,11 @@ export const switchCaseExecutor: StepExecutor<"switch-case"> = { } const branchScope = lastUpdate?.scope ?? scope; yield { - scope: { ...scope, ...branchScope }, + scope: { + ...scope, + ...branchScope, + [step.id]: lastUpdate?.output ?? null, + }, output: null, error: null, ...(step.nextStepId diff --git a/packages/core/src/execution/step-output-contract.test.ts b/packages/core/src/execution/step-output-contract.test.ts index d0e2c11..f8dcb40 100644 --- a/packages/core/src/execution/step-output-contract.test.ts +++ b/packages/core/src/execution/step-output-contract.test.ts @@ -208,11 +208,13 @@ const CONTRACT_CASES: Record = { ], }, }), - step("body", { type: "start" }), + step("body", { + type: "end", + params: { output: { type: "literal", value: "picked" } }, + }), ], - // `switch-case` merges the branch scope but binds nothing under its own id. - expectedOutput: undefined, - conformance: "verified", + expectedOutput: "picked", + conformance: "unconstrained", }, "wait-for-condition": { steps: [ diff --git a/packages/core/src/generation/index.test.ts b/packages/core/src/generation/index.test.ts index bf590e9..334059c 100644 --- a/packages/core/src/generation/index.test.ts +++ b/packages/core/src/generation/index.test.ts @@ -1,7 +1,11 @@ -import { expect, test } from "bun:test"; +import { describe, expect, test } from "bun:test"; import { APICallError } from "ai"; import { MockLanguageModelV4 } from "ai/test"; +import { type } from "arktype"; +import { createScriptedMockModel } from "../execution/test-support"; +import { step, workflow } from "../workflow-fixtures"; import { + generateWorkflow, generateWorkflowStream, type WorkflowGenerationDiagnosticEvent, } from "./index"; @@ -38,8 +42,8 @@ function createRepairingThenHangingModel() { content: [ { type: "tool-call" as const, - toolCallId: "invalid-submission", - toolName: "submit-workflow", + toolCallId: "invalid-write", + toolName: "write-workflow", input: JSON.stringify({ definition: { initialStepId: "start", @@ -152,10 +156,11 @@ test("reports provider retries and invalid tool calls", async () => { return { content: [ + { type: "reasoning" as const, text: "Write a workflow." }, { type: "tool-call" as const, - toolCallId: "malformed-submission", - toolName: "submit-workflow", + toolCallId: "malformed-write", + toolName: "write-workflow", input: "{}", }, ], @@ -244,10 +249,18 @@ test("reports provider retries and invalid tool calls", async () => { stepNumber: 0, finishReason: "tool-calls", rawFinishReason: "tool_calls", + reasoningText: "Write a workflow.", + toolCalls: [ + { + toolCallId: "malformed-write", + toolName: "write-workflow", + status: "invalid", + }, + ], invalidToolCalls: [ { - toolCallId: "malformed-submission", - toolName: "submit-workflow", + toolCallId: "malformed-write", + toolName: "write-workflow", input: {}, error: { name: "AI_InvalidToolInputError", @@ -265,7 +278,7 @@ test("reports provider retries and invalid tool calls", async () => { expect(stepEnd.invalidToolCalls[0]?.error.message).not.toContain("Value:"); }); -test("requests closed input generation for workflow submissions", async () => { +test("requests closed input generation for workflow writes", async () => { let submittedTools: Parameters< MockLanguageModelV4["doGenerate"] >[0]["tools"]; @@ -302,10 +315,10 @@ test("requests closed input generation for workflow submissions", async () => { const result = await stream.next(); expect(result.done).toBe(true); - const submitWorkflowTool = submittedTools?.find( - (tool) => tool.name === "submit-workflow", + const writeWorkflowTool = submittedTools?.find( + (tool) => tool.name === "write-workflow", ); - expect(submitWorkflowTool).toMatchObject({ + expect(writeWorkflowTool).toMatchObject({ type: "function", strict: true, inputSchema: { @@ -316,3 +329,346 @@ test("requests closed input generation for workflow submissions", async () => { }, }); }); + +const cleanWorkflow = workflow( + step("start", { type: "start", nextStepId: "done" }), + step("done", { type: "end" }), +); +/** Valid, but warns because its first step is not a "start" step. */ +const warningWorkflow = workflow(step("done", { type: "end" })); + +/** Runs generation with a model that makes one tool call per step. */ +async function generateScripted( + calls: Array<{ toolName: string; input: unknown }>, + overrides: Partial[0]> = {}, +) { + const model = createScriptedMockModel( + calls.map((call, index) => ({ + type: "tool-calls" as const, + calls: [{ toolCallId: `call-${index + 1}`, ...call }], + })), + ); + const events: WorkflowGenerationDiagnosticEvent[] = []; + const result = await generateWorkflow({ + taskDescription: "Test.", + tools: {}, + options: {}, + model, + maxGenerationSteps: 5, + onDiagnosticEvent: (event) => events.push(event), + ...overrides, + }); + const toolCalls = events.flatMap((event) => + event.type === "step-end" ? event.toolCalls : [], + ); + /** The result of a tool call, as the model received it. */ + const toolOutput = (toolCallId: string) => + model.doGenerateCalls + .flatMap((call) => call.prompt) + .flatMap((message) => + message.role === "tool" ? message.content : [], + ) + .find( + (part) => + part.type === "tool-result" && + part.toolCallId === toolCallId, + ); + return { model, result, toolCalls, toolOutput }; +} + +test("writes, edits, and submits a workflow", async () => { + const draft = workflow( + step("start", { type: "start", nextStepId: "missing" }), + ); + const operations = [ + { op: "add", path: "/steps/-", value: step("done", { type: "end" }) }, + { op: "replace", path: "/steps/0/nextStepId", value: "done" }, + ]; + const model = createScriptedMockModel([ + { + type: "tool-calls", + calls: [ + { + toolCallId: "call-1", + toolName: "write-workflow", + input: { definition: draft }, + }, + ], + }, + { + type: "tool-calls", + calls: [ + { + toolCallId: "call-2", + toolName: "edit-workflow", + input: { operations }, + }, + ], + }, + { + type: "tool-calls", + calls: [ + { + toolCallId: "call-3", + toolName: "submit-workflow", + input: {}, + }, + ], + }, + ]); + const stream = generateWorkflowStream({ + taskDescription: "Create an empty workflow.", + tools: {}, + options: {}, + model, + maxGenerationSteps: 5, + }); + + const yielded: unknown[] = []; + let result = await stream.next(); + while (!result.done) { + yielded.push(result.value); + result = await stream.next(); + } + + expect(yielded).toEqual([draft, cleanWorkflow, cleanWorkflow]); + expect(result.value).toEqual({ + gaveUp: false, + reason: null, + workflowDefinition: cleanWorkflow, + }); + const toolResults = model.doGenerateCalls[2]?.prompt.flatMap((message) => + message.role === "tool" ? message.content : [], + ); + expect(toolResults).toMatchObject([ + { + toolCallId: "call-1", + output: { + type: "json", + value: { + diagnostics: [ + { severity: "error", path: ["steps", 0, "nextStepId"] }, + ], + submitted: false, + }, + }, + }, + { + toolCallId: "call-2", + output: { + type: "json", + value: { + definition: cleanWorkflow, + diagnostics: [], + submitted: false, + }, + }, + }, + ]); +}); + +test("records the input and status of each tool call", async () => { + const { toolCalls } = await generateScripted([ + { toolName: "write-workflow", input: { definition: cleanWorkflow } }, + { toolName: "submit-workflow", input: {} }, + ]); + + expect(toolCalls).toEqual([ + { + toolCallId: "call-1", + toolName: "write-workflow", + input: { definition: cleanWorkflow }, + status: "succeeded", + }, + { + toolCallId: "call-2", + toolName: "submit-workflow", + input: {}, + status: "succeeded", + }, + ]); +}); + +test("rejects a submission before a workflow is written", async () => { + const { result, toolCalls, toolOutput } = await generateScripted([ + { toolName: "submit-workflow", input: {} }, + { toolName: "give-up", input: { reason: "Test complete." } }, + ]); + + expect(result.gaveUp).toBe(true); + expect(toolCalls[0]).toMatchObject({ status: "failed" }); + expect(toolOutput("call-1")).toMatchObject({ + output: { + type: "error-text", + value: "Error: No workflow exists yet. Use write-workflow to write one.", + }, + }); +}); + +test("submitIfValid ends generation after a clean write", async () => { + const { model, result } = await generateScripted([ + { + toolName: "write-workflow", + input: { definition: cleanWorkflow, submitIfValid: true }, + }, + ]); + + expect(model.doGenerateCalls).toHaveLength(1); + expect(result.workflowDefinition).toEqual(cleanWorkflow); +}); + +test("submitIfValid does not submit a workflow that has warnings", async () => { + const { result, toolOutput } = await generateScripted([ + { + toolName: "write-workflow", + input: { definition: warningWorkflow, submitIfValid: true }, + }, + { toolName: "give-up", input: { reason: "Test complete." } }, + ]); + + expect(toolOutput("call-1")).toMatchObject({ + output: { value: { submitted: false } }, + }); + expect(result.gaveUp).toBe(true); +}); + +test("submit-workflow accepts warnings only with ignoreWarnings", async () => { + const { result, toolCalls } = await generateScripted([ + { toolName: "write-workflow", input: { definition: warningWorkflow } }, + { toolName: "submit-workflow", input: {} }, + { toolName: "submit-workflow", input: { ignoreWarnings: true } }, + ]); + + expect(toolCalls.map((toolCall) => toolCall.status)).toEqual([ + "succeeded", + "failed", + "succeeded", + ]); + expect(result.workflowDefinition).toEqual(warningWorkflow); +}); + +test("reports the error of a failed tool call", async () => { + const { toolCalls } = await generateScripted([ + { toolName: "write-workflow", input: { definition: cleanWorkflow } }, + { + toolName: "edit-workflow", + input: { operations: [{ op: "remove", path: "/steps/3" }] }, + }, + { toolName: "give-up", input: { reason: "Test complete." } }, + ]); + + expect(toolCalls[1]).toMatchObject({ + toolName: "edit-workflow", + status: "failed", + input: { operations: [{ op: "remove", path: "/steps/3" }] }, + error: { + message: + 'Patch operation 0 ({"op":"remove","path":"/steps/3"}) failed with OPERATION_PATH_UNRESOLVABLE.', + }, + }); +}); + +test("marks the instructions as an Anthropic cache breakpoint", async () => { + const { model } = await generateScripted( + [{ toolName: "give-up", input: { reason: "Test complete." } }], + { maxGenerationSteps: 1 }, + ); + + expect(model.doGenerateCalls[0]?.prompt[0]).toMatchObject({ + role: "system", + providerOptions: { + anthropic: { cacheControl: { type: "ephemeral" } }, + }, + }); +}); + +test("repairs arguments that were sent as JSON strings", async () => { + const { model, result } = await generateScripted([ + { + toolName: "write-workflow", + input: { definition: JSON.stringify(cleanWorkflow) }, + }, + { + toolName: "edit-workflow", + input: { + operations: JSON.stringify([ + { op: "replace", path: "/steps/1/name", value: "End" }, + ]), + submitIfValid: true, + }, + }, + ]); + + expect(model.doGenerateCalls).toHaveLength(2); + expect(result.workflowDefinition?.steps[1]?.name).toBe("End"); +}); + +describe("required output schema", () => { + const routed = (outputSchema?: object) => ({ + ...workflow( + step("start", { type: "start", nextStepId: "done" }), + step("done", { + type: "end", + params: { + output: { type: "literal", value: { route: "review" } }, + }, + }), + ), + ...(outputSchema ? { outputSchema } : {}), + }); + const workflowOutputSchema = type({ route: "'review' | 'on-call'" }); + + test("is used when the workflow declares no output schema", async () => { + const { model, result } = await generateScripted( + [ + { + toolName: "write-workflow", + input: { definition: routed(), submitIfValid: true }, + }, + ], + { workflowOutputSchema }, + ); + + expect(model.doGenerateCalls).toHaveLength(1); + expect(result.workflowDefinition?.outputSchema).toMatchObject({ + type: "object", + required: ["route"], + }); + }); + + test("rejects a declared output schema that is broader", async () => { + const { toolOutput } = await generateScripted( + [ + { + toolName: "write-workflow", + input: { + definition: routed({ + type: "object", + properties: { route: { type: "string" } }, + required: ["route"], + }), + submitIfValid: true, + }, + }, + { toolName: "give-up", input: { reason: "Test complete." } }, + ], + { workflowOutputSchema }, + ); + + expect(toolOutput("call-1")).toMatchObject({ + output: { + value: { + diagnostics: [ + { + severity: "error", + message: expect.stringContaining( + "must be a subset of the required output schema", + ), + }, + ], + submitted: false, + }, + }, + }); + }); +}); diff --git a/packages/core/src/generation/index.ts b/packages/core/src/generation/index.ts index 95040ae..0aede1a 100644 --- a/packages/core/src/generation/index.ts +++ b/packages/core/src/generation/index.ts @@ -4,14 +4,15 @@ import { type FlexibleSchema, generateText, hasToolCall, - jsonSchema, + NoSuchToolError, stepCountIs, + type ToolCallRepairFunction, + type ToolSet, tool, wrapLanguageModel, } from "ai"; import { type } from "arktype"; import dedent from "dedent"; -import type { JSONSchema7 } from "json-schema"; import type { StandardJSONSchemaV1 } from ".."; import { createAsyncQueue } from "../execution/execution-engine/async-queue"; import { @@ -26,10 +27,7 @@ import { } from "../types"; import { validateWorkflowDefinition } from "../validation"; import { requestedOutputSchemaDiagnostics } from "./output-schema"; -import { - findLastSuccessfulToolCall, - hasSuccessfulToolCall, -} from "./stop-condition"; +import { WorkflowEditor } from "./workflow-editor"; export type GenerationOptions = RemoraflowSettings & {}; @@ -96,7 +94,21 @@ export type WorkflowGenerationDiagnosticEvent = stepNumber: number; finishReason: string; rawFinishReason?: string; + reasoningText?: string; performance: unknown; + toolCalls: Array< + { + toolCallId: string; + toolName: string; + input: unknown; + } & ( + | { status: "succeeded" | "invalid" } + | { + status: "failed"; + error: WorkflowGenerationDiagnosticError; + } + ) + >; invalidToolCalls: Array<{ toolCallId: string; toolName: string; @@ -242,6 +254,50 @@ function serializeInvalidToolCallError( }; } +/** + * Repairs a tool call whose top-level arguments were sent as JSON strings where + * the tool's input schema expects an object or an array. + */ +const parseStringifiedArguments: ToolCallRepairFunction = async ({ + toolCall, + inputSchema, + error, +}) => { + if (NoSuchToolError.isInstance(error)) return null; + let input: unknown; + try { + input = JSON.parse(toolCall.input); + } catch { + return null; + } + if (typeof input !== "object" || input === null) return null; + + const { properties = {} } = await inputSchema({ + toolName: toolCall.toolName, + }); + const repairedInput: Record = { ...input }; + let repaired = false; + for (const [key, value] of Object.entries(input)) { + const property = properties[key]; + const expectsContainer = + typeof property === "object" && + [property.type] + .flat() + .some((type) => type === "object" || type === "array"); + if (typeof value !== "string" || !expectsContainer) continue; + try { + const parsed: unknown = JSON.parse(value); + if (typeof parsed === "object" && parsed !== null) { + repairedInput[key] = parsed; + repaired = true; + } + } catch {} + } + return repaired + ? { ...toolCall, input: JSON.stringify(repairedInput) } + : null; +}; + export function preparePrompt({ taskDescription, workflowOutputSchema, @@ -255,12 +311,18 @@ export function preparePrompt({ }) { const { workflowDefinitionArktypeSchema } = createWorkflowDefinitionSchema(options); + const requiredOutputSchema = workflowOutputSchema?.[ + "~standard" + ].jsonSchema.input({ target: "draft-07" }); const instructions = dedent` You are a workflow generation subagent. You generate workflows from a task description and a set of predefined tools. Workflows are written using a proprietary "Remoraflow" JSON definition. Notes: - - Workflows undergo a validation step after submission. If your workflow fails this validation, remediate and resubmit. + - Write the workflow with write-workflow. It returns the validation diagnostics of the workflow. + - To fix diagnostics, change the workflow with edit-workflow (a JSON Patch). Do not write the full workflow again. Use read-workflow to look at parts of the workflow. + - Set submitIfValid on write-workflow or edit-workflow when you expect the workflow to be complete. The workflow is then submitted if it has no errors or warnings. + - To submit a workflow that has only warnings, call submit-workflow with ignoreWarnings. `; const prompt = dedent` @@ -294,11 +356,11 @@ export function preparePrompt({ ${ - workflowOutputSchema + requiredOutputSchema ? dedent` The generated workflow must declare and produce output matching this JSON Schema: - ${JSON.stringify(workflowOutputSchema["~standard"].jsonSchema.input({ target: "draft-07" }))} + ${JSON.stringify(requiredOutputSchema)} ` : "" @@ -309,6 +371,7 @@ export function preparePrompt({ instructions, prompt, workflowDefinitionArktypeSchema, + requiredOutputSchema, }; } @@ -335,44 +398,72 @@ export async function* generateWorkflowStream({ onDiagnosticEvent?: (event: WorkflowGenerationDiagnosticEvent) => void; strictToolCalls?: boolean; }): AsyncGenerator, GenerationOutput> { - const { instructions, prompt, workflowDefinitionArktypeSchema } = - preparePrompt({ - taskDescription, - workflowOutputSchema, - tools, - options, - }); - const resolvedOptions = remoraflowSettingsSchema.assert(options); - const submitWorkflowInputArktypeSchema = type({ - "+": "reject", - definition: workflowDefinitionArktypeSchema, - "ignoreWarnings?": [ - "boolean", - "@", - "Some workflow definitions will produce warnings during validation. These warnings will cause the workflow to be rejected. You should generally attempt to resolve these warnings, but if they are minor or difficult to fix, you can set this to `true` and the warnings will be ignored while errors will continue to be caught.", - ], + const { + instructions, + prompt, + workflowDefinitionArktypeSchema, + requiredOutputSchema, + } = preparePrompt({ + taskDescription, + workflowOutputSchema, + tools, + options, }); - const submitWorkflowInputSchema = jsonSchema<{ - definition: WorkflowDefinition; - ignoreWarnings?: boolean; - }>( - submitWorkflowInputArktypeSchema.toJsonSchema({ - target: "draft-07", - fallback: (ctx) => ctx.base, - }) as JSONSchema7, - { - validate: (value) => { - const result = submitWorkflowInputArktypeSchema(value); - return result instanceof type.errors - ? { success: false, error: new Error(result.summary) } - : { success: true, value: result }; - }, - }, + const resolvedOptions = remoraflowSettingsSchema.assert(options); + const validateDraft = (draft: unknown) => { + const validation = validateWorkflowDefinition( + (requiredOutputSchema && + typeof draft === "object" && + draft !== null && + !("outputSchema" in draft) + ? { ...draft, outputSchema: requiredOutputSchema } + : draft) as WorkflowDefinition, + { tools, options: resolvedOptions }, + ); + if (!validation.isValid || !requiredOutputSchema) return validation; + const outputSchemaDiagnostics = requestedOutputSchemaDiagnostics( + validation.correctedDefinition.outputSchema, + requiredOutputSchema, + ).map(({ path, message }) => ({ + severity: "error" as const, + path, + message: `The workflow's output schema must be a subset of the required output schema: ${message}`, + })); + return { + ...validation, + isValid: outputSchemaDiagnostics.length === 0, + diagnostics: [ + ...validation.diagnostics, + ...outputSchemaDiagnostics, + ], + }; + }; + let acceptedDefinition: WorkflowDefinition | undefined; + const submitDraft = (draft: unknown, ignoreWarnings = false) => { + const { isValid, diagnostics, correctedDefinition } = + validateDraft(draft); + const accepted = + isValid && + (ignoreWarnings || + !diagnostics.some( + (diagnostic) => diagnostic.severity === "warning", + )); + if (accepted) acceptedDefinition = correctedDefinition; + return { accepted, diagnostics }; + }; + const editor = new WorkflowEditor( + workflowDefinitionArktypeSchema, + (draft) => validateDraft(draft).diagnostics, + (draft) => submitDraft(draft).accepted, ); + let lastYieldedDraft: unknown; const yieldQueue = createAsyncQueue< | { type: "final-output"; payload: WorkflowDefinition } - | { type: "intermediate-output"; payload: WorkflowDefinition } + | { + type: "intermediate-output"; + payload: DeepPartial; + } | { type: "give-up"; payload: string } | { type: "error"; payload: string } >(); @@ -380,7 +471,6 @@ export async function* generateWorkflowStream({ const generationAbortSignal = abortSignal ? AbortSignal.any([abortSignal, generationAbortController.signal]) : generationAbortController.signal; - const acceptedWorkflowDefinitions = new Map(); const generationStartedAt = Date.now(); const providerAttemptsByStep = new Map(); let currentStepNumber = 0; @@ -453,55 +543,39 @@ export async function* generateWorkflowStream({ generateText({ model: diagnosticModel, abortSignal: generationAbortSignal, - instructions, + instructions: { + role: "system", + content: instructions, + // Other providers ignore options in the `anthropic` namespace. + providerOptions: { + anthropic: { cacheControl: { type: "ephemeral" } }, + }, + }, prompt, tools: { + ...editor.getTools({ strict: strictToolCalls }), "submit-workflow": tool({ - description: "Submits a candidate workflow for validation", + description: + "Submits the current workflow for final validation.", strict: strictToolCalls, - inputSchema: submitWorkflowInputSchema, - execute: async ( - { definition, ignoreWarnings }, - { toolCallId }, - ) => { - yieldQueue.push({ - type: "intermediate-output", - payload: definition, - }); - const { isValid, diagnostics, correctedDefinition } = - validateWorkflowDefinition(definition, { - tools, - options: resolvedOptions, - }); - if ( - !isValid || - (!ignoreWarnings && - diagnostics.some( - (diagnostic) => - diagnostic.severity === "warning", - )) - ) { + inputSchema: type({ + "+": "reject", + "ignoreWarnings?": [ + "boolean", + "@", + "Some workflow definitions will produce warnings during validation. These warnings will cause the workflow to be rejected. You should generally attempt to resolve these warnings, but if they are minor or difficult to fix, you can set this to `true` and the warnings will be ignored while errors will continue to be caught.", + ], + }), + execute: ({ ignoreWarnings }) => { + const { accepted, diagnostics } = submitDraft( + editor.requireDraft(), + ignoreWarnings, + ); + if (!accepted) { throw new Error( `Workflow rejected with the following diagnostics: ${JSON.stringify(diagnostics)}`, ); - } else if (workflowOutputSchema) { - const subsetDiagnostics = - requestedOutputSchemaDiagnostics( - correctedDefinition.outputSchema, - workflowOutputSchema[ - "~standard" - ].jsonSchema.input({ target: "draft-07" }), - ); - if (subsetDiagnostics.length > 0) { - throw new Error( - `Workflow's output schema must be a valid subset of ${JSON.stringify(workflowOutputSchema)}, but subset validation produced the following diagnostics: ${JSON.stringify(subsetDiagnostics)}`, - ); - } } - acceptedWorkflowDefinitions.set( - toolCallId, - correctedDefinition, - ); return "Workflow validated successfully."; }, }), @@ -516,6 +590,7 @@ export async function* generateWorkflowStream({ }, }), }, + repairToolCall: parseStringifiedArguments, timeout: { totalMs: timeoutMs, }, @@ -538,6 +613,14 @@ export async function* generateWorkflowStream({ }); }, onStepEnd: (step) => { + const draft = editor.getDraft(); + if (draft !== undefined && draft !== lastYieldedDraft) { + lastYieldedDraft = draft; + yieldQueue.push({ + type: "intermediate-output", + payload: draft as DeepPartial, + }); + } emitDiagnostic({ type: "step-end", atMs: elapsedMs(), @@ -546,7 +629,37 @@ export async function* generateWorkflowStream({ ...(step.rawFinishReason ? { rawFinishReason: step.rawFinishReason } : {}), + ...(step.reasoningText + ? { reasoningText: step.reasoningText } + : {}), performance: step.performance, + toolCalls: step.toolCalls.map((toolCall) => { + const call = { + toolCallId: toolCall.toolCallId, + toolName: toolCall.toolName, + input: toolCall.input, + }; + if (toolCall.invalid) { + return { ...call, status: "invalid" }; + } + const toolError = step.content.find( + ( + part, + ): part is Extract< + typeof part, + { type: "tool-error" } + > => + part.type === "tool-error" && + part.toolCallId === call.toolCallId, + ); + return toolError + ? { + ...call, + status: "failed", + error: serializeDiagnosticError(toolError.error), + } + : { ...call, status: "succeeded" }; + }), invalidToolCalls: step.toolCalls .filter((toolCall) => toolCall.invalid) .map((toolCall) => ({ @@ -559,24 +672,12 @@ export async function* generateWorkflowStream({ }, stopWhen: [ stepCountIs(maxGenerationSteps), - hasSuccessfulToolCall("submit-workflow"), + () => acceptedDefinition !== undefined, hasToolCall("give-up"), ], }) .then((res) => { - const successfulSubmission = findLastSuccessfulToolCall( - "submit-workflow", - res.steps, - ); - if (successfulSubmission) { - const acceptedDefinition = acceptedWorkflowDefinitions.get( - successfulSubmission.toolCallId, - ); - if (!acceptedDefinition) { - throw new Error( - "A successful workflow submission had no accepted definition.", - ); - } + if (acceptedDefinition) { yieldQueue.push({ type: "final-output", payload: acceptedDefinition, diff --git a/packages/core/src/generation/stop-condition.test.ts b/packages/core/src/generation/stop-condition.test.ts deleted file mode 100644 index c0675ec..0000000 --- a/packages/core/src/generation/stop-condition.test.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import type { StepResult, ToolSet } from "ai"; -import { - findLastSuccessfulToolCall, - hasSuccessfulToolCall, -} from "./stop-condition"; - -function step({ - calls = [], - results = [], -}: { - calls?: Array<{ id: string; name: string }>; - results?: Array<{ id: string; name: string }>; -}): StepResult { - return { - toolCalls: calls.map(({ id, name }) => ({ - type: "tool-call", - toolCallId: id, - toolName: name, - input: {}, - })), - toolResults: results.map(({ id, name }) => ({ - type: "tool-result", - toolCallId: id, - toolName: name, - input: {}, - output: "ok", - })), - } as unknown as StepResult; -} - -describe("hasSuccessfulToolCall", () => { - test("is false when no matching tool was called", () => { - expect(hasSuccessfulToolCall("submit")({ steps: [step({})] })).toBe( - false, - ); - }); - - test("is false when matching tool execution produced no result", () => { - expect( - hasSuccessfulToolCall("submit")({ - steps: [step({ calls: [{ id: "failed", name: "submit" }] })], - }), - ).toBe(false); - }); - - test("is true only when the matching call has a successful result", () => { - const successfulStep = step({ - calls: [{ id: "accepted", name: "submit" }], - results: [{ id: "accepted", name: "submit" }], - }); - - expect( - hasSuccessfulToolCall("submit")({ steps: [successfulStep] }), - ).toBe(true); - expect( - findLastSuccessfulToolCall("submit", [successfulStep])?.toolCallId, - ).toBe("accepted"); - }); - - test("does not pair a call with another call's result", () => { - const mismatchedStep = step({ - calls: [{ id: "failed", name: "submit" }], - results: [{ id: "other", name: "submit" }], - }); - - expect( - hasSuccessfulToolCall("submit")({ steps: [mismatchedStep] }), - ).toBe(false); - }); -}); diff --git a/packages/core/src/generation/stop-condition.ts b/packages/core/src/generation/stop-condition.ts deleted file mode 100644 index 882e58a..0000000 --- a/packages/core/src/generation/stop-condition.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { StepResult, StopCondition, ToolSet, TypedToolCall } from "ai"; - -export function findLastSuccessfulToolCall( - toolName: string, - steps: Array>, -): TypedToolCall | undefined { - for (const step of steps.toReversed()) { - const successfulToolCallIds = new Set( - step.toolResults.map((result) => result.toolCallId), - ); - const matchingCall = step.toolCalls.findLast( - (toolCall) => - toolCall.toolName === toolName && - successfulToolCallIds.has(toolCall.toolCallId), - ); - if (matchingCall) return matchingCall; - } - return undefined; -} - -export const hasSuccessfulToolCall: ( - toolName: string, -) => StopCondition = - (toolName) => - ({ steps }) => - Boolean(findLastSuccessfulToolCall(toolName, steps)); diff --git a/packages/core/src/generation/workflow-editor.test.ts b/packages/core/src/generation/workflow-editor.test.ts new file mode 100644 index 0000000..62cae75 --- /dev/null +++ b/packages/core/src/generation/workflow-editor.test.ts @@ -0,0 +1,219 @@ +import { describe, expect, test } from "bun:test"; +import { asSchema, type FlexibleSchema } from "ai"; +import { + createWorkflowDefinitionSchema, + type WorkflowDefinition, +} from "../schema"; +import { validateWorkflowDefinition } from "../validation"; +import { step, workflow } from "../workflow-fixtures"; +import { WorkflowEditor } from "./workflow-editor"; + +describe("WorkflowEditor", () => { + const executionOptions = { + toolCallId: "call", + messages: [], + } as never; + + function createEditor(initialDraft?: WorkflowDefinition) { + const submitted: unknown[] = []; + const editor = new WorkflowEditor( + createWorkflowDefinitionSchema().workflowDefinitionArktypeSchema, + (draft) => + validateWorkflowDefinition(draft as WorkflowDefinition, { + tools: {}, + }).diagnostics, + (draft) => { + submitted.push(draft); + return true; + }, + initialDraft, + ); + const tools = editor.getTools(); + const run = (name: string, input: unknown) => + tools[name]?.execute?.(input as never, executionOptions); + return { editor, tools, run, submitted }; + } + + const invalidWorkflow: WorkflowDefinition = workflow( + step("start", { type: "start", nextStepId: "missing" }), + ); + + test("returns only the write, read, and edit tools", () => { + expect(Object.keys(createEditor().tools)).toEqual([ + "write-workflow", + "read-workflow", + "edit-workflow", + ]); + }); + + test("write returns error diagnostics without throwing", async () => { + const { editor, run } = createEditor(); + const result = await run("write-workflow", { + definition: invalidWorkflow, + }); + expect(result.diagnostics).toMatchObject([ + { severity: "error", path: ["steps", 0, "nextStepId"] }, + ]); + expect(editor.getDraft()).toEqual(invalidWorkflow); + }); + + test("edit applies a patch and returns new diagnostics", async () => { + const { editor, run, submitted } = createEditor(invalidWorkflow); + const result = await run("edit-workflow", { + operations: [ + { + op: "add", + path: "/steps/-", + value: step("done", { type: "end" }), + }, + { op: "replace", path: "/steps/0/nextStepId", value: "done" }, + ], + }); + const fixed = workflow( + step("start", { type: "start", nextStepId: "done" }), + step("done", { type: "end" }), + ); + expect(result).toEqual({ + definition: fixed, + diagnostics: [], + submitted: false, + }); + expect(editor.getDraft()).toEqual(fixed); + expect(submitted).toEqual([]); + }); + + test("submitIfValid submits a write or an edit", async () => { + const { run, submitted } = createEditor(); + expect( + await run("write-workflow", { + definition: invalidWorkflow, + submitIfValid: true, + }), + ).toMatchObject({ submitted: true }); + expect( + await run("edit-workflow", { + operations: [ + { op: "replace", path: "/steps/0/name", value: "Begin" }, + ], + submitIfValid: true, + }), + ).toMatchObject({ submitted: true }); + expect(submitted).toMatchObject([ + invalidWorkflow, + { steps: [{ name: "Begin" }] }, + ]); + }); + + test("edit that makes the structure invalid returns diagnostics", async () => { + const { editor, run } = createEditor(invalidWorkflow); + const result = await run("edit-workflow", { + operations: [ + { op: "replace", path: "/steps/0/type", value: "unknown" }, + ], + }); + expect(result.diagnostics).toMatchObject([ + { + severity: "error", + message: expect.stringContaining("steps[0].type must be"), + }, + ]); + expect(editor.getDraft()).toMatchObject({ + steps: [{ type: "unknown" }], + }); + }); + + test("failed patch does not change the draft", async () => { + const { editor, run } = createEditor(invalidWorkflow); + expect(() => + run("edit-workflow", { + operations: [ + { op: "remove", path: "/steps/0/nextStepId" }, + { op: "test", path: "/initialStepId", value: "other" }, + ], + }), + ).toThrow( + 'Patch operation 1 ({"op":"test","path":"/initialStepId","value":"other"}) failed with TEST_OPERATION_FAILED.', + ); + expect(editor.getDraft()).toEqual(invalidWorkflow); + }); + + test("replace adds a field that does not exist yet", async () => { + const { editor, run } = createEditor( + workflow(step("start", { type: "start" })), + ); + await run("edit-workflow", { + operations: [ + { op: "replace", path: "/steps/0/nextStepId", value: "done" }, + ], + }); + expect(editor.getDraft()).toMatchObject({ + steps: [{ nextStepId: "done" }], + }); + }); + + test("replace under a missing parent reports the original error", async () => { + const { editor, run } = createEditor(invalidWorkflow); + expect(() => + run("edit-workflow", { + operations: [ + { op: "replace", path: "/steps/0/name", value: "Begin" }, + { op: "replace", path: "/steps/5/name", value: "x" }, + ], + }), + ).toThrow( + 'Patch operation 1 ({"op":"replace","path":"/steps/5/name","value":"x"}) failed with OPERATION_PATH_UNRESOLVABLE.', + ); + expect(editor.getDraft()).toEqual(invalidWorkflow); + }); + + test("edit input schema accepts only JSON Patch operations", async () => { + const { tools } = createEditor(); + const validate = (value: unknown) => + asSchema( + tools["edit-workflow"]?.inputSchema as FlexibleSchema, + ).validate?.(value); + + expect( + await validate({ + operations: [{ op: "move", from: "/a", path: "/b" }], + }), + ).toMatchObject({ success: true }); + expect( + await validate({ + operations: [{ op: "remove", path: "/a", value: 1 }], + }), + ).toMatchObject({ success: false }); + expect( + await validate({ operations: [{ op: "merge", path: "/a" }] }), + ).toMatchObject({ success: false }); + expect(await validate({ operations: [] })).toMatchObject({ + success: false, + }); + }); + + test("read returns the full draft or a query result", async () => { + const { run } = createEditor(invalidWorkflow); + expect(await run("read-workflow", {})).toEqual(invalidWorkflow); + expect( + await run("read-workflow", { jmespathQuery: "steps[*].id" }), + ).toEqual(["start"]); + }); + + test("read and edit fail before a write", () => { + const { run } = createEditor(); + expect(() => run("read-workflow", {})).toThrow("write-workflow"); + expect(() => + run("edit-workflow", { + operations: [{ op: "add", path: "/steps/-", value: {} }], + }), + ).toThrow("write-workflow"); + }); + + test("tool input schemas convert to JSON Schema", async () => { + for (const tool of Object.values(createEditor().tools)) { + expect( + await asSchema(tool.inputSchema as FlexibleSchema).jsonSchema, + ).toMatchObject({ type: "object", additionalProperties: false }); + } + }); +}); diff --git a/packages/core/src/generation/workflow-editor.ts b/packages/core/src/generation/workflow-editor.ts new file mode 100644 index 0000000..70f213c --- /dev/null +++ b/packages/core/src/generation/workflow-editor.ts @@ -0,0 +1,187 @@ +import { jsonSchema, type ToolSet, tool } from "ai"; +import { type Type, type } from "arktype"; +import { + applyOperation, + JsonPatchError, + type Operation, +} from "fast-json-patch"; +import { search } from "jmespath"; +import type { JSONSchema7 } from "json-schema"; +import type { WorkflowDefinition } from "../schema"; +import type { ValidatorDiagnostic } from "../validation"; + +export class WorkflowEditor { + private workflowDefinitionSchema: Type; + private diagnose: (draft: unknown) => ValidatorDiagnostic[]; + private submit: (draft: unknown) => boolean; + private workflowDraft: unknown; + + /** + * @param submit Called when a write or edit sets `submitIfValid`. Returns + * whether the draft was accepted. + */ + constructor( + workflowDefinitionSchema: Type, + diagnose: (draft: unknown) => ValidatorDiagnostic[], + submit: (draft: unknown) => boolean, + initialDraft?: WorkflowDefinition, + ) { + this.workflowDefinitionSchema = workflowDefinitionSchema; + this.diagnose = diagnose; + this.submit = submit; + this.workflowDraft = initialDraft; + } + + /** Edits can make the draft invalid, so its type is `unknown`. */ + public getDraft(): unknown { + return this.workflowDraft; + } + + public requireDraft(): unknown { + if (this.workflowDraft === undefined) { + throw new Error( + "No workflow exists yet. Use write-workflow to write one.", + ); + } + return this.workflowDraft; + } + + public getTools({ strict = false }: { strict?: boolean } = {}): ToolSet { + const submitIfValid = [ + "boolean", + "@", + "Submit the workflow for final validation if it has no errors or warnings. Set this when you expect the workflow to be complete.", + ] as const; + const writeWorkflowInputSchema = type({ + "+": "reject", + definition: this.workflowDefinitionSchema, + "submitIfValid?": submitIfValid, + }) as Type<{ definition: WorkflowDefinition; submitIfValid?: boolean }>; + return { + "write-workflow": tool({ + description: + "Writes a full workflow definition and replaces the current workflow. Returns the validation diagnostics of the new workflow, and whether it was submitted.", + strict, + // The workflow definition schema contains predicates that JSON Schema cannot express. + inputSchema: jsonSchema( + writeWorkflowInputSchema.toJsonSchema({ + target: "draft-07", + fallback: (ctx) => ctx.base, + }) as JSONSchema7, + { + validate: (value) => { + const result = writeWorkflowInputSchema(value); + return result instanceof type.errors + ? { + success: false, + error: new Error(result.summary), + } + : { success: true, value: result }; + }, + }, + ), + execute: ({ definition, submitIfValid }) => { + this.workflowDraft = definition; + return { + diagnostics: this.diagnose(definition), + submitted: + submitIfValid === true && this.submit(definition), + }; + }, + }), + "read-workflow": tool({ + description: + "Returns the current workflow definition, or the part of it that a JMESPath query selects.", + inputSchema: type({ + "+": "reject", + "jmespathQuery?": [ + "string", + "@", + "A JMESPath query that selects the part of the workflow to return. Omit it to return the full workflow.", + ], + }), + execute: ({ jmespathQuery }) => { + const draft = this.requireDraft(); + return jmespathQuery === undefined + ? draft + : search(draft, jmespathQuery); + }, + }), + "edit-workflow": tool({ + description: + "Applies a JSON Patch to the current workflow. Returns the changed workflow, its validation diagnostics, and whether it was submitted.", + inputSchema: type({ + "+": "reject", + operations: [ + type({ + "+": "reject", + op: "'add' | 'replace' | 'test'", + path: "string", + value: "unknown", + }) + .or({ + "+": "reject", + op: "'remove'", + path: "string", + }) + .or({ + "+": "reject", + op: "'move' | 'copy'", + from: "string", + path: "string", + }) + .array() + .atLeastLength(1), + "@", + "A JSON Patch (RFC 6902). Paths are JSON Pointers (RFC 6901), for example /steps/0/params. Use /steps/- to append a step. The operations are applied in sequence. If an operation fails, the workflow does not change.", + ], + "submitIfValid?": submitIfValid, + }), + execute: ({ operations, submitIfValid }) => { + let document = structuredClone(this.requireDraft()); + const tryApply = (operation: Operation) => { + try { + document = applyOperation( + document, + operation, + true, + true, + ).newDocument; + return undefined; + } catch (error) { + if (error instanceof JsonPatchError) return error; + throw error; + } + }; + for (const [index, operation] of ( + operations as Operation[] + ).entries()) { + let error = tryApply(operation); + // Models often `replace` a field that does not exist yet, which RFC 6902 rejects. + if ( + error?.name === "OPERATION_PATH_UNRESOLVABLE" && + operation.op === "replace" && + !tryApply({ ...operation, op: "add" }) + ) { + error = undefined; + } + if (error) { + // JsonPatchError messages contain the full document, so they are too long for a tool result. + throw new Error( + `Patch operation ${index} (${JSON.stringify(operation)}) failed with ${error.name}.`, + ); + } + } + this.workflowDraft = document; + return { + definition: this.workflowDraft, + diagnostics: this.diagnose(this.workflowDraft), + submitted: + submitIfValid === true && + this.submit(this.workflowDraft), + }; + }, + }), + }; + } +} diff --git a/packages/core/src/generation/workflow-generation.e2e.test.ts b/packages/core/src/generation/workflow-generation.e2e.test.ts index 4903d1c..6069cee 100644 --- a/packages/core/src/generation/workflow-generation.e2e.test.ts +++ b/packages/core/src/generation/workflow-generation.e2e.test.ts @@ -1,6 +1,8 @@ import { describe, expect, test } from "bun:test"; -import { createOpenAI } from "@ai-sdk/openai"; +import { createAmazonBedrock } from "@ai-sdk/amazon-bedrock"; +import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { jsonSchemaToType } from "@ark/json-schema"; +import { fromNodeProviderChain } from "@aws-sdk/credential-providers"; import type { DeepPartial } from "ai"; import { type } from "arktype"; import type { JSONSchema7 } from "json-schema"; @@ -15,15 +17,42 @@ import { } from "./index"; import { requestedOutputSchemaDiagnostics } from "./output-schema"; +const provider = process.env.E2E_PROVIDER ?? "openrouter"; const apiKey = process.env.OPENROUTER_API_KEY; -const modelId = process.env.OPENROUTER_MODEL_ID; -const describeLive = apiKey && modelId ? describe : describe.skip; - -const openrouter = createOpenAI({ +const modelId = + provider === "bedrock" + ? process.env.BEDROCK_MODEL_ID + : process.env.OPENROUTER_MODEL_ID; +const reasoning = process.env.OPENROUTER_REASONING; +const describeLive = + modelId && (provider === "bedrock" || apiKey) ? describe : describe.skip; + +const openrouter = createOpenAICompatible({ + name: "openrouter", apiKey: apiKey ?? "missing-openrouter-api-key", baseURL: "https://openrouter.ai/api/v1", + supportsStructuredOutputs: true, + // "off" disables reasoning; "low", "medium", or "high" sets its effort. + ...(reasoning + ? { + transformRequestBody: (body: Record) => ({ + ...body, + reasoning: + reasoning === "off" + ? { enabled: false } + : { effort: reasoning }, + }), + } + : {}), +}); +const bedrock = createAmazonBedrock({ + region: process.env.AWS_REGION, + credentialProvider: fromNodeProviderChain(), }); -const model = openrouter.chat(modelId ?? "missing-openrouter-model-id"); +const model = + provider === "bedrock" + ? bedrock(modelId ?? "missing-bedrock-model-id") + : openrouter.chatModel(modelId ?? "missing-openrouter-model-id"); const GENERATION_TIMEOUT_MS = 180_000; const GENERATION_OUTER_TIMEOUT_MS = 210_000; @@ -121,6 +150,7 @@ async function collectGenerationOutput( continue; } + logToolCalls(scenarioName, generationDiagnostics); if (next.value.gaveUp) { logWorkflowAttempts( scenarioName, @@ -132,6 +162,7 @@ async function collectGenerationOutput( return next.value; } } catch (error) { + logToolCalls(scenarioName, generationDiagnostics); logWorkflowAttempts( scenarioName, attempts, @@ -140,10 +171,44 @@ async function collectGenerationOutput( ); throw error; } finally { + logUsage( + scenarioName, + generationDiagnostics, + Date.now() - generationStartedAt, + ); await stream.return(undefined as never); } } +type ProviderUsage = { + inputTokens?: { total?: number; cacheRead?: number }; + outputTokens?: { total?: number; reasoning?: number }; +}; + +function logUsage( + scenarioName: string, + generationDiagnostics: WorkflowGenerationDiagnosticEvent[], + elapsedMs: number, +) { + const usages = generationDiagnostics.flatMap((event) => + event.type === "provider-attempt-end" + ? [event.usage as ProviderUsage] + : [], + ); + const sum = (tokens: (usage: ProviderUsage) => number | undefined) => + usages.reduce((total, usage) => total + (tokens(usage) ?? 0), 0); + console.log( + `[workflow generation usage] ${scenarioName}: ${JSON.stringify({ + elapsedMs, + providerCalls: usages.length, + inputTokens: sum((usage) => usage.inputTokens?.total), + cachedInputTokens: sum((usage) => usage.inputTokens?.cacheRead), + outputTokens: sum((usage) => usage.outputTokens?.total), + reasoningTokens: sum((usage) => usage.outputTokens?.reasoning), + })}`, + ); +} + function logWorkflowAttempts( scenarioName: string, attempts: GenerationAttempt[], @@ -159,6 +224,47 @@ function logWorkflowAttempts( ); } +function formatToolCall( + toolCall: Extract< + WorkflowGenerationDiagnosticEvent, + { type: "step-end" } + >["toolCalls"][number], +): string { + // A full definition is too long for this summary. + const { definition, operations, ...options } = + typeof toolCall.input === "object" && toolCall.input !== null + ? (toolCall.input as Record) + : {}; + const args = [ + ...(Array.isArray(operations) + ? operations.map( + ({ op, path }: { op: string; path: string }) => + `${op} ${path}`, + ) + : []), + ...Object.entries(options).map( + ([name, value]) => `${name}: ${JSON.stringify(value)}`, + ), + ]; + const call = `${toolCall.toolName}(${args.join(", ")})`; + if (toolCall.status === "failed") { + return `${call} [failed: ${toolCall.error.message}]`; + } + return toolCall.status === "invalid" ? `${call} [invalid]` : call; +} + +function logToolCalls( + scenarioName: string, + generationDiagnostics: WorkflowGenerationDiagnosticEvent[], +) { + const toolCalls = generationDiagnostics.flatMap((event) => + event.type === "step-end" ? event.toolCalls.map(formatToolCall) : [], + ); + console.log( + `[workflow generation tool calls] ${scenarioName}: ${toolCalls.join(" → ") || "none"}`, + ); +} + function logAcceptedWorkflow( scenarioName: string, executionCaseName: string, diff --git a/packages/core/src/schema.ts b/packages/core/src/schema.ts index bb2649e..1147598 100644 --- a/packages/core/src/schema.ts +++ b/packages/core/src/schema.ts @@ -144,10 +144,13 @@ const switchCaseParamsSchema = type({ } \`\`\` - After this step completes, execution continues from its \`nextStepId\` when present. + The value returned by the selected branch becomes this step's output. After this step completes, execution continues from its \`nextStepId\` when present. `, ); +const ACCUMULATOR_GUIDANCE = + "To collect each iteration's output into an array, omit the accumulator. In fold mode, the body's output replaces the accumulator, so to append an item to an array accumulator named `acc`, return `[acc, [item]][]`. The expression `[acc[], item]` does not append: it puts the previous accumulator inside the new array."; + const forEachParamsSchema = type({ ...baseStepProperties, type: "'for-each'", @@ -216,6 +219,8 @@ const forEachParamsSchema = type({ } \`\`\` + ${ACCUMULATOR_GUIDANCE} + After this step completes, execution continues from its \`nextStepId\` when present. `, ); @@ -285,6 +290,8 @@ const whileParamsSchema = type({ } \`\`\` + ${ACCUMULATOR_GUIDANCE} + After this step completes, execution continues from its \`nextStepId\` when present. `, ); @@ -552,29 +559,24 @@ export function createWorkflowDefinitionSchema( "a step that pauses execution to ask the supervising user how to proceed", ); - let workflowStepArktypeSchema = toolCallParamsSchema - .or(llmPromptSchema) - .or(extractDataParamsSchema) - .or(switchCaseParamsSchema) - .or(forEachParamsSchema) - .or(whileParamsSchema) - .or(sleepParamsSchema) - .or(waitForConditionParamsSchema) - .or(agentLoopParamsSchema) - .or(requestInterventionParamsSchema) - .or(startParamsSchema) - .or(endParamsSchema); - - if (!options.features.allowAgentLoops) { - workflowStepArktypeSchema = workflowStepArktypeSchema.exclude( - agentLoopParamsSchema, - ); - } - if (!options.features.allowUserIntervention) { - workflowStepArktypeSchema = workflowStepArktypeSchema.exclude( - requestInterventionParamsSchema, - ); - } + // One n-ary union is much faster to build than a chain of `.or()` calls + // followed by `.exclude()`, because arktype reduces each intermediate union. + const workflowStepArktypeSchema = type.or( + toolCallParamsSchema, + llmPromptSchema, + extractDataParamsSchema, + switchCaseParamsSchema, + forEachParamsSchema, + whileParamsSchema, + sleepParamsSchema, + waitForConditionParamsSchema, + ...(options.features.allowAgentLoops ? [agentLoopParamsSchema] : []), + ...(options.features.allowUserIntervention + ? [requestInterventionParamsSchema] + : []), + startParamsSchema, + endParamsSchema, + ); /** Schema for validating workflow definitions. */ const workflowDefinitionArktypeSchema = type({ diff --git a/packages/core/src/schemistry/index.ts b/packages/core/src/schemistry/index.ts index 99d54f8..ba63575 100644 --- a/packages/core/src/schemistry/index.ts +++ b/packages/core/src/schemistry/index.ts @@ -6,6 +6,7 @@ export { inferQueryOutputSchema, unionSchemas, } from "./jmespath/infer"; +export { JMESPATH_FUNCTION_NAMES } from "./jmespath/types"; export { inferJsonSchema, type JsonSchema } from "./json-schema/from-value"; export { type SubsetDiagnostic, diff --git a/packages/core/src/schemistry/jmespath/infer.test.ts b/packages/core/src/schemistry/jmespath/infer.test.ts index 973d218..0b59691 100644 --- a/packages/core/src/schemistry/jmespath/infer.test.ts +++ b/packages/core/src/schemistry/jmespath/infer.test.ts @@ -180,6 +180,22 @@ describe("inferQueryOutputSchema", () => { badAccess: "maybe", }); }); + + test("access into a union whose members all have the field resolves cleanly", () => { + const schema: JSONSchema7Definition = { + anyOf: [ + stringA, + { ...stringA, properties: { a: { const: "x" } } }, + ], + }; + expect(inferQueryOutputSchema(schema, "a").schema).toEqual({ + anyOf: [ + { type: "string", badAccess: "false" }, + { const: "x", badAccess: "false" }, + ], + badAccess: "false", + }); + }); }); describe("projections and flatten", () => { @@ -223,6 +239,29 @@ describe("inferQueryOutputSchema", () => { }); }); + test("flatten concatenates a list of arrays", () => { + const schema: JSONSchema7Definition = { + type: "object", + properties: { + empty: { type: "array", items: false }, + item: { type: "string" }, + }, + required: ["empty", "item"], + }; + expect( + inferQueryOutputSchema(schema, "[empty, [item]][]").schema, + ).toEqual({ + type: "array", + items: { type: "string", badAccess: "false" }, + badAccess: "false", + }); + expect(inferQueryOutputSchema(schema, "empty[]").schema).toEqual({ + type: "array", + items: false, + badAccess: "false", + }); + }); + test("pipe composes sequential access", () => { expect(inferQueryOutputSchema(arrayOfB, "a | [0]").schema).toEqual({ type: "object", diff --git a/packages/core/src/schemistry/jmespath/infer.ts b/packages/core/src/schemistry/jmespath/infer.ts index 86090be..dd6b5dd 100644 --- a/packages/core/src/schemistry/jmespath/infer.ts +++ b/packages/core/src/schemistry/jmespath/infer.ts @@ -28,7 +28,8 @@ export type BadAccess = "true" | "maybe" | "false"; */ export interface AnnotatedSchema extends JSONSchema7 { badAccess?: BadAccess; - items?: AnnotatedSchema | AnnotatedSchema[]; + /** `false` for an array that has no elements. */ + items?: AnnotatedSchema | AnnotatedSchema[] | false; properties?: { [key: string]: AnnotatedSchema }; additionalProperties?: AnnotatedSchema | boolean; anyOf?: AnnotatedSchema[]; @@ -177,7 +178,13 @@ export function inferQueryOutputSchema( if (isArraySchema(base)) { return { type: "array", - items: inferNode(node.children[1], arrayElement(base)), + items: + base.items === false + ? false + : inferNode( + node.children[1], + arrayElement(base), + ), badAccess: "false", }; } @@ -223,11 +230,18 @@ export function inferQueryOutputSchema( if (!isArraySchema(base)) { return badNull(); } - const element = arrayElement(base); - const items = isArraySchema(element) - ? arrayElement(element) - : element; - return { type: "array", items, badAccess: "false" }; + const flattened = arrayElementMembers(base).flatMap( + (element) => + isArraySchema(element) + ? arrayElementMembers(element) + : [element], + ); + return { + type: "array", + items: + flattened.length > 0 ? unionSchemas(flattened) : false, + badAccess: "false", + }; } case "MultiSelectList": return { @@ -595,12 +609,19 @@ function accessWithNormalization( const everyMemberFails = accessed.every( (member) => member.badAccess === "true", ); + const everyMemberSucceeds = accessed.every( + (member) => member.badAccess === "false", + ); return { anyOf: accessed.map((member) => ({ ...member, badAccess: "false" as const, })), - badAccess: everyMemberFails ? "true" : "maybe", + badAccess: everyMemberFails + ? "true" + : everyMemberSucceeds + ? "false" + : "maybe", }; } return single(s); @@ -746,6 +767,16 @@ function arrayElement(schema: AnnotatedSchema): AnnotatedSchema { return {}; } +/** The alternatives an element of `schema` can take; none for an empty array. */ +function arrayElementMembers(schema: AnnotatedSchema): AnnotatedSchema[] { + const { items } = schema; + if (items === false) return []; + const elements = Array.isArray(items) + ? items.map(asSchemaObject) + : [items === undefined ? {} : asSchemaObject(items)]; + return elements.flatMap((element) => unionMembers(element) ?? [element]); +} + function objectValueUnion(schema: AnnotatedSchema): AnnotatedSchema { const values = Object.values(schema.properties ?? {}).map(asSchemaObject); const additional = schema.additionalProperties; diff --git a/packages/core/src/schemistry/jmespath/types.ts b/packages/core/src/schemistry/jmespath/types.ts index 47a43b2..1e233d6 100644 --- a/packages/core/src/schemistry/jmespath/types.ts +++ b/packages/core/src/schemistry/jmespath/types.ts @@ -24,3 +24,33 @@ export const compileExpression: (expression: string) => ExpressionNode = // @types/jmespath omits compile, but the runtime exports it // biome-ignore lint/suspicious/noExplicitAny: runtime API not in type defs (jmespath as any).compile; + +/** Must match the functions that the `jmespath` runtime defines. */ +export const JMESPATH_FUNCTION_NAMES = [ + "abs", + "avg", + "ceil", + "contains", + "ends_with", + "floor", + "join", + "keys", + "length", + "map", + "max", + "max_by", + "merge", + "min", + "min_by", + "not_null", + "reverse", + "sort", + "sort_by", + "starts_with", + "sum", + "to_array", + "to_number", + "to_string", + "type", + "values", +] as const; diff --git a/packages/core/src/schemistry/json-schema/from-value.ts b/packages/core/src/schemistry/json-schema/from-value.ts index 95dbf81..c2a416d 100644 --- a/packages/core/src/schemistry/json-schema/from-value.ts +++ b/packages/core/src/schemistry/json-schema/from-value.ts @@ -9,6 +9,9 @@ export function inferJsonSchema(value: unknown): JsonSchema { if (value === null) { return { type: "null" }; } else if (Array.isArray(value)) { + if (value.length === 0) { + return { type: "array", items: false }; + } const itemTypes = value.map((item) => inferJsonSchema(item)); const itemTypesHashable = itemTypes.map((itemType) => JSON.stringify(itemType), diff --git a/packages/core/src/schemistry/json-schema/subset.test.ts b/packages/core/src/schemistry/json-schema/subset.test.ts index 7e43389..36bc43c 100644 --- a/packages/core/src/schemistry/json-schema/subset.test.ts +++ b/packages/core/src/schemistry/json-schema/subset.test.ts @@ -421,4 +421,72 @@ describe("schemaSubsetDiagnostics", () => { schemaSubsetDiagnostics(closed({ x: { type: "number" } }), sup), ).toEqual([]); }); + + test("a union is a subset when every member is", () => { + const sup: JSONSchema7Definition = { + type: "array", + items: closed({ id: { type: "string" } }), + }; + expect( + schemaSubsetDiagnostics( + { + anyOf: [ + { type: "array", items: false }, + { + type: "array", + items: closed({ id: { type: "string" } }), + }, + ], + }, + sup, + ), + ).toEqual([]); + expect( + levelsByPath( + { + anyOf: [ + { type: "array", items: false }, + { type: "array", items: { type: "number" } }, + ], + }, + sup, + ), + ).toEqual([{ level: "warning", path: [] }]); + expect( + levelsByPath( + { anyOf: [{ type: "number" }, { type: "boolean" }] }, + { type: "string" }, + ), + ).toEqual([ + { level: "error", path: [] }, + { level: "error", path: [] }, + ]); + }); + + test("checks each tuple item against a list item schema", () => { + expect( + levelsByPath( + { + type: "array", + items: [{ type: "string" }, { type: "number" }], + }, + { type: "array", items: { type: "string" } }, + ), + ).toEqual([{ level: "error", path: ["items", 1] }]); + }); + + test("different JSON Schema types are disjoint, except integer and number", () => { + expect( + levelsByPath(closed({ id: { type: "string" } }), { + type: "array", + items: { type: "object" }, + }), + ).toEqual([{ level: "error", path: [] }]); + expect( + schemaSubsetDiagnostics({ type: "integer" }, { type: "number" }), + ).toEqual([]); + expect(levelsByPath({ type: "number" }, { type: "integer" })).toEqual([ + { level: "warning", path: [] }, + ]); + }); }); diff --git a/packages/core/src/schemistry/json-schema/subset.ts b/packages/core/src/schemistry/json-schema/subset.ts index 5bb1f90..a66e31b 100644 --- a/packages/core/src/schemistry/json-schema/subset.ts +++ b/packages/core/src/schemistry/json-schema/subset.ts @@ -64,6 +64,24 @@ function getDiagnostics( }, ]; } + // A union is a subset when every member is. A member that can never + // satisfy the target makes the union possibly invalid, not definitely so. + if ( + typeof sub === "object" && + Array.isArray(sub.anyOf) && + !hasUnhandledKeyword(sub, UNION_KEYWORDS) + ) { + const memberDiagnostics = sub.anyOf.map((member) => + getDiagnostics(member, sup, path), + ); + const everyMemberFails = memberDiagnostics.every((diagnostics) => + diagnostics.some((diagnostic) => diagnostic.level === "error"), + ); + if (everyMemberFails) return memberDiagnostics.flat(); + return memberDiagnostics.some((diagnostics) => diagnostics.length > 0) + ? [leaf(sub, sup, path, "overlap")] + : []; + } // A literal value is validated exactly against the whole target — no // structural recursion, and never "overlap" (a single value matches or not). if (typeof sub === "object" && "const" in sub) { @@ -76,6 +94,16 @@ function getDiagnostics( if (isArraySchema(sub) && isArraySchema(sup)) { return arrayDiagnostics(sub, sup, path); } + // Different JSON Schema types are disjoint, but integer and number + // overlap. arktype treats arrays as objects, so it cannot find this. + if ( + typeof sub.type === "string" && + typeof sup.type === "string" && + sub.type !== sup.type && + !(NUMERIC_TYPES.has(sub.type) && NUMERIC_TYPES.has(sup.type)) + ) { + return [leaf(sub, sup, path, "disjoint")]; + } } const relationship = classify(sub, sup); return relationship === "subset" @@ -191,6 +219,17 @@ function arrayDiagnostics( ...getDiagnostics(subItem, supItem, [...path, "items", i]), ); } + } else if ( + Array.isArray(subItems) && + supItems && + !Array.isArray(supItems) && + typeof supItems === "object" + ) { + for (const [i, subItem] of subItems.entries()) { + diagnostics.push( + ...getDiagnostics(subItem, supItems, [...path, "items", i]), + ); + } } else if ( subItems && supItems && @@ -384,6 +423,8 @@ const HANDLED_OBJECT_KEYWORDS = new Set([ /** Array keywords fully handled by the structural walk in `arrayDiagnostics`. */ const HANDLED_ARRAY_KEYWORDS = new Set(["type", "items"]); +const UNION_KEYWORDS = new Set(["anyOf"]); +const NUMERIC_TYPES = new Set(["integer", "number"]); /** Keys that carry no validation meaning; a schema with only these is unknown. */ const ANNOTATION_KEYS = new Set([ diff --git a/packages/core/src/validation/expression-syntax-validation/index.ts b/packages/core/src/validation/expression-syntax-validation/index.ts index 9f5a822..d199717 100644 --- a/packages/core/src/validation/expression-syntax-validation/index.ts +++ b/packages/core/src/validation/expression-syntax-validation/index.ts @@ -1,51 +1,88 @@ import type { Expression } from "../../schema"; -import { compileExpression } from "../../schemistry/jmespath/types"; +import { + compileExpression, + type ExpressionNode, + JMESPATH_FUNCTION_NAMES, +} from "../../schemistry/jmespath/types"; import { extractTemplateInserts } from "../../schemistry/template"; import { expressionReferences } from "../../step-registry"; import type { ValidationModule, ValidatorDiagnostic } from "../types"; +const knownFunctionNames = new Set(JMESPATH_FUNCTION_NAMES); + function describeParseError(error: unknown): string { const message = error instanceof Error ? error.message : String(error); return message.split("\n")[0] ?? message; } +function unknownFunctionNames(node: ExpressionNode): string[] { + const own = + node.type === "Function" && !knownFunctionNames.has(node.name) + ? [node.name] + : []; + // Slice nodes hold numbers and nulls as children. + const children = (node.children ?? []).filter( + (child): child is ExpressionNode => + typeof child === "object" && child !== null, + ); + return [...own, ...children.flatMap(unknownFunctionNames)]; +} + +function validateJmespath( + source: string, + path: PropertyKey[], + context: string, + diagnostics: ValidatorDiagnostic[], +) { + try { + for (const name of unknownFunctionNames(compileExpression(source))) { + diagnostics.push({ + severity: "error", + path, + message: `Unknown JMESPath function "${name}()"${context}. The available functions are: ${JMESPATH_FUNCTION_NAMES.join(", ")}.`, + }); + } + } catch (error) { + diagnostics.push({ + severity: "error", + path, + message: `Invalid JMESPath expression${context}: ${describeParseError(error)}`, + }); + } +} + function validateExpression( expression: Expression, - path: ValidatorDiagnostic["path"], + path: PropertyKey[], diagnostics: ValidatorDiagnostic[], ) { switch (expression.type) { case "jmespath": { - try { - compileExpression(expression.expression); - } catch (error) { - diagnostics.push({ - severity: "error", - path: [...(path ?? []), "expression"], - message: `Invalid JMESPath expression: ${describeParseError(error)}`, - }); - } + validateJmespath( + expression.expression, + [...path, "expression"], + "", + diagnostics, + ); break; } case "template": { + const templatePath = [...path, "template"]; try { for (const insert of extractTemplateInserts( expression.template, )) { - try { - compileExpression(insert.expression); - } catch (error) { - diagnostics.push({ - severity: "error", - path: [...(path ?? []), "template"], - message: `Invalid JMESPath expression in template: ${describeParseError(error)}`, - }); - } + validateJmespath( + insert.expression, + templatePath, + " in template", + diagnostics, + ); } } catch (error) { diagnostics.push({ severity: "error", - path: [...(path ?? []), "template"], + path: templatePath, message: `Invalid template string: ${describeParseError(error)}`, }); } diff --git a/packages/core/src/validation/validators.test.ts b/packages/core/src/validation/validators.test.ts index b222d6b..151b0c1 100644 --- a/packages/core/src/validation/validators.test.ts +++ b/packages/core/src/validation/validators.test.ts @@ -1296,6 +1296,31 @@ describe("switch-case branch bindings", () => { ]); }); + test("types the switch output as the union of its branch outputs", () => { + const { isValid, diagnostics } = validateWorkflowDefinition( + readingAfterSwitch("pick.n"), + ctx(), + ); + expect(isValid).toBe(true); + expect(diagnostics.filter((d) => d.message.includes("access"))).toEqual( + [], + ); + expect( + errorsIn( + validateWorkflowDefinition( + readingAfterSwitch("pick.nope"), + ctx(), + ).diagnostics, + ), + ).toEqual([ + { + severity: "error", + path: ["steps", 3, "params", "output", "expression"], + message: "Invalid access: always resolves to null.", + }, + ]); + }); + test("still errors on a field the branch output never has", () => { // Nullability must not swallow genuine mistakes. const { isValid, diagnostics } = validateWorkflowDefinition( @@ -1442,3 +1467,178 @@ describe("the poll interval floor at author time", () => { expect(errorsIn(diagnostics)).toEqual([]); }); }); + +describe("expression function names", () => { + const outputting = (output: Expression) => + workflow( + step("start", { type: "start", nextStepId: "done" }), + step("done", { type: "end", params: { output } }), + ); + + test("rejects a function that JMESPath does not define", () => { + const { isValid, diagnostics } = validateWorkflowDefinition( + outputting({ + type: "jmespath", + expression: "sort(`[1]` | append(`2`))", + }), + ctx(), + ); + expect(isValid).toBe(false); + expect(errorsIn(diagnostics)).toEqual([ + { + severity: "error", + path: ["steps", 1, "params", "output", "expression"], + message: expect.stringContaining( + 'Unknown JMESPath function "append()"', + ), + }, + ]); + }); + + test("rejects an unknown function in a template insert", () => { + const { diagnostics } = validateWorkflowDefinition( + outputting({ type: "template", template: "${upper(`a`)}" }), + ctx(), + ); + expect(errorsIn(diagnostics)).toMatchObject([ + { + path: ["steps", 1, "params", "output", "template"], + message: expect.stringContaining('"upper()"'), + }, + ]); + }); + + test("accepts built-in functions, including inside slices", () => { + const { diagnostics } = validateWorkflowDefinition( + outputting({ + type: "jmespath", + expression: 'length(sort_by(`[{"a": 1}]`, &a)[0:1])', + }), + ctx(), + ); + expect(errorsIn(diagnostics)).toEqual([]); + }); +}); + +describe("loop accumulator type", () => { + const jobTools: StubbedToolSet = { + "run-job": tool({ + inputSchema: type({ jobId: "string" }), + outputSchema: type({ jobId: "string" }), + execute: async ({ jobId }) => ({ jobId }), + }), + }; + const loopParams = { + "for-each": { + target: { type: "literal", value: [{ jobId: "a" }] }, + itemName: "job", + }, + while: { conditionStepId: "check" }, + } as const; + + for (const loopType of ["for-each", "while"] as const) { + describe(loopType, () => { + const accumulating = ( + collect: string, + initialValue: unknown = [], + ): WorkflowDefinition => ({ + ...workflow( + step("start", { type: "start", nextStepId: "loop" }), + step("loop", { + type: loopType, + nextStepId: "done", + params: { + ...loopParams[loopType], + loopBodyStepId: "run", + accumulatorName: "results", + accumulatorInitialValue: { + type: "literal", + value: initialValue, + }, + }, + } as never), + step("run", { + type: "tool-call", + nextStepId: "collect", + params: { + toolName: "run-job", + toolInput: { + jobId: { type: "literal", value: "a" }, + }, + }, + }), + step("collect", { + type: "end", + params: { + output: { type: "jmespath", expression: collect }, + }, + }), + step("done", { + type: "end", + params: { + output: { type: "jmespath", expression: "loop" }, + }, + }), + ...(loopType === "while" + ? [ + step("check", { + type: "end", + params: { + output: { type: "literal", value: false }, + }, + }), + ] + : []), + ), + outputSchema: { + type: "array", + items: { + type: "object", + properties: { jobId: { type: "string" } }, + required: ["jobId"], + }, + }, + }); + const diagnosticsFor = (collect: string, initialValue?: unknown) => + validateWorkflowDefinition( + accumulating(collect, initialValue), + ctx(jobTools), + ).diagnostics; + const doneOutput = ["steps", 4, "params", "output"]; + const initialValuePath = [ + "steps", + 1, + "params", + "accumulatorInitialValue", + ]; + + test("reports a body output that does not match as an error", () => { + // Without appending, the loop outputs only the last result. + expect(diagnosticsFor("run")).toMatchObject([ + { severity: "warning", path: initialValuePath }, + { severity: "error", path: doneOutput }, + ]); + }); + + test("reports a body output that nests the previous results", () => { + expect(diagnosticsFor("[results[], run]")).toMatchObject([ + { severity: "error", path: [...doneOutput, "items", 0] }, + ]); + }); + + test("accepts an accumulator that appends to an empty array", () => { + expect(diagnosticsFor("[results, [run]][]")).toEqual([]); + }); + + test("warns when the initial value does not match the body's output", () => { + expect(diagnosticsFor("[run]", null)).toEqual([ + { + severity: "warning", + path: initialValuePath, + message: expect.stringContaining("never runs"), + }, + ]); + }); + }); + } +}); diff --git a/packages/core/src/validation/variable-reference-validation/index.ts b/packages/core/src/validation/variable-reference-validation/index.ts index 62c4b09..911b55f 100644 --- a/packages/core/src/validation/variable-reference-validation/index.ts +++ b/packages/core/src/validation/variable-reference-validation/index.ts @@ -10,6 +10,7 @@ import { extractTemplateInserts, inferJsonSchema, inferQueryOutputSchema, + schemaSubsetDiagnostics, unionSchemas, } from "../../schemistry"; import { @@ -191,7 +192,9 @@ export function getStepOutputType( return inputSchema ?? { type: "null" }; } case "switch-case": { - return { type: "null" }; + // The switch-case block processor narrows this to the union of its + // branch outputs. + return true; } case "tool-call": { const tool = tools[step.params.toolName]; @@ -292,6 +295,11 @@ export type ScopeSnapshots = { * runs, the scope that expression sees. */ nestedChainScopeByStepId: Map; + /** For a loop with an accumulator, its initial type and its body's output type. */ + accumulatorTypesByStepId: Map< + string, + { initialType: RemoraflowType; bodyOutputType: RemoraflowType } + >; }; type BlockScopeProcessor = (args: { @@ -312,6 +320,28 @@ type ChainAnalysis = { outputType: RemoraflowType; }; +function bindLoopOutput( + node: StepGraphNode, + scope: TypeScope, + snapshots: ScopeSnapshots, + accumulatorInitialType: RemoraflowType | undefined, + bodyOutputType: RemoraflowType, +): ChainAnalysis { + // This type assumes that the body runs at least once. A different check + // compares the initial value with the body output and gives a warning. + const outputType: RemoraflowType = accumulatorInitialType + ? bodyOutputType + : { type: "array", items: bodyOutputType }; + if (accumulatorInitialType) { + snapshots.accumulatorTypesByStepId.set(node.stepId, { + initialType: accumulatorInitialType, + bodyOutputType, + }); + } + scope.bindings.set(node.stepId, outputType); + return { scope, outputType }; +} + /** * How each block step type scopes its nested chains and which bindings escape * into the continuation. Any type with a processor here must declare its chains @@ -320,7 +350,7 @@ type ChainAnalysis = { const blockScopeProcessors: { [T in WorkflowStep["type"]]: BlockScopeProcessor | null; } = { - "for-each": ({ node, step, scope, walkChain }) => { + "for-each": ({ node, step, scope, snapshots, walkChain }) => { const targetType = getExpressionType( step.params.target, scope, @@ -357,17 +387,13 @@ const blockScopeProcessors: { } const bodyAnalysis = walkChain(bodyNode, loopScope); - let outputType: RemoraflowType; - if (hasAccumulator && accInitType) { - outputType = accInitType; - } else { - outputType = { - type: "array", - items: bodyAnalysis.outputType, - }; - } - scope.bindings.set(node.stepId, outputType); - return { scope, outputType }; + return bindLoopOutput( + node, + scope, + snapshots, + accInitType, + bodyAnalysis.outputType, + ); }, "switch-case": ({ node, scope, walkChain }) => { const branchAnalyses = blockEntranceNodes(node).map((branchNode) => @@ -379,14 +405,16 @@ const blockScopeProcessors: { outputType: scope.bindings.get(node.stepId) ?? true, }; } + const outputType = unionSchemas( + branchAnalyses.map((analysis) => analysis.outputType), + ); + scope.bindings.set(node.stepId, outputType); return { scope: mergeBranchScopes( scope, branchAnalyses.map((analysis) => analysis.scope), ), - outputType: unionSchemas( - branchAnalyses.map((analysis) => analysis.outputType), - ), + outputType, }; }, "wait-for-condition": ({ node, scope, snapshots, walkChain }) => { @@ -409,7 +437,7 @@ const blockScopeProcessors: { outputType: scope.bindings.get(node.stepId) ?? true, }; }, - while: ({ node, step, scope, walkChain }) => { + while: ({ node, step, scope, snapshots, walkChain }) => { const [conditionNode, bodyNode] = blockEntranceNodes(node); const hasAccumulator = step.params.accumulatorName !== undefined; @@ -442,17 +470,13 @@ const blockScopeProcessors: { hasAccumulator ? accScope() : scope, ); - let outputType: RemoraflowType; - if (hasAccumulator && accInitType) { - outputType = accInitType; - } else { - outputType = { - type: "array", - items: bodyAnalysis.outputType, - }; - } - scope.bindings.set(node.stepId, outputType); - return { scope, outputType }; + return bindLoopOutput( + node, + scope, + snapshots, + accInitType, + bodyAnalysis.outputType, + ); }, "agent-loop": null, "request-intervention": null, @@ -538,6 +562,7 @@ export function buildScopeSnapshotsById( const snapshots: ScopeSnapshots = { byStepId: new Map(), nestedChainScopeByStepId: new Map(), + accumulatorTypesByStepId: new Map(), }; const initialBindings = new Map(); if (workflowDefinition.inputSchema) { @@ -653,6 +678,23 @@ export function validateVariableReferences( validateExpressionReferences(ref.expression, refPath); } } + const accumulatorTypes = scopeSnapshots.accumulatorTypesByStepId.get( + step.id, + ); + if ( + accumulatorTypes && + schemaSubsetDiagnostics( + accumulatorTypes.initialType, + accumulatorTypes.bodyOutputType, + ).length > 0 + ) { + diagnostics.push({ + severity: "warning", + path: ["steps", stepIndex, "params", "accumulatorInitialValue"], + message: + "If the loop body never runs, the loop outputs this initial value, but its type does not match the type of the loop body's output.", + }); + } } return diagnostics; } diff --git a/packages/ui/src/syntax/codemirror-template-highlighting.ts b/packages/ui/src/syntax/codemirror-template-highlighting.ts index 863d278..e1c422a 100644 --- a/packages/ui/src/syntax/codemirror-template-highlighting.ts +++ b/packages/ui/src/syntax/codemirror-template-highlighting.ts @@ -5,38 +5,12 @@ import { ViewPlugin, type ViewUpdate, } from "@codemirror/view"; +import { JMESPATH_FUNCTION_NAMES } from "@remoraflow/core"; type Range = { from: number; to: number }; export type TemplateSyntaxToken = Range & { className: string }; -const JMESPATH_BUILT_INS = new Set([ - "abs", - "avg", - "ceil", - "contains", - "ends_with", - "floor", - "join", - "keys", - "length", - "map", - "max", - "max_by", - "merge", - "min", - "min_by", - "not_null", - "reverse", - "sort", - "sort_by", - "starts_with", - "sum", - "to_array", - "to_number", - "to_string", - "type", - "values", -]); +const JMESPATH_BUILT_INS = new Set(JMESPATH_FUNCTION_NAMES); /** * Finds Remora template interpolations using the same brace and quote rules as diff --git a/packages/ui/src/syntax/jmespath.ts b/packages/ui/src/syntax/jmespath.ts index 724d132..80c1236 100644 --- a/packages/ui/src/syntax/jmespath.ts +++ b/packages/ui/src/syntax/jmespath.ts @@ -1,3 +1,5 @@ +import { JMESPATH_FUNCTION_NAMES } from "@remoraflow/core"; + export type HighlightMode = { className?: string; begin?: RegExp; @@ -23,8 +25,7 @@ export type HighlightLanguage = HighlightMode & { const JMESPATH_KEYWORDS = { $pattern: /[A-Za-z_][A-Za-z0-9_]*/, - built_in: - "abs avg ceil contains ends_with floor join keys length map max max_by merge min min_by not_null reverse sort sort_by starts_with sum to_array to_number to_string type values", + built_in: JMESPATH_FUNCTION_NAMES.join(" "), literal: "false null true", };