mirror of
https://github.com/dograh-hq/dograh.git
synced 2026-06-07 07:55:16 +02:00
feat: refactor node spec and add mcp tools (#244)
* refactor: carve out extraction panel * refactor: create spec versions for node types * refactor: create a GenericNode and remove custom nodes * feat: add python and typescript sdk * add dograh sdk * fix: fetch draft workflow definition over published one * fix: fix routes of SDKs to use code gen * chore: remove doclink dependency to reduce image size * chore: format files * chore: bump pipecat * feat: let mcp fetch archived workflows on demand * chore: fix tests * feat: add sdk documentation * chore: change banner and add badge
This commit is contained in:
parent
0a61ef295f
commit
00a1a22b74
162 changed files with 14355 additions and 3554 deletions
109
scripts/generate_sdk.sh
Executable file
109
scripts/generate_sdk.sh
Executable file
|
|
@ -0,0 +1,109 @@
|
|||
#!/usr/bin/env bash
|
||||
# Regenerate every file the SDKs derive from authoritative backend state:
|
||||
#
|
||||
# 1. Typed node dataclasses / TS interfaces (from node_specs registry)
|
||||
# 2. Filtered OpenAPI spec (routes tagged via @sdk_expose)
|
||||
# 3. Pydantic request/response models + TS interfaces (datamodel-codegen
|
||||
# / openapi-typescript)
|
||||
# 4. Client method mixins (_generated_client.py / _generated_client.ts)
|
||||
#
|
||||
# Run from anywhere — the script resolves the repo root relative to itself.
|
||||
# Requires:
|
||||
# - `python` in the `dograh` conda env, `api/.env` sourced; the `api`
|
||||
# package must be importable. `datamodel-code-generator` installed
|
||||
# (`pip install datamodel-code-generator`).
|
||||
# - `node` (>= 22.6 for native .mts support) and npm. openapi-typescript
|
||||
# is a devDependency of sdk/typescript; `npm install` in that dir is
|
||||
# done for you if node_modules is missing.
|
||||
#
|
||||
# Invoked manually after editing any NodeSpec or after adding/removing an
|
||||
# `@sdk_expose` decorator. CI runs this and asserts the git diff is empty.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
if [ -f "$REPO_ROOT/api/.env" ]; then
|
||||
set -a
|
||||
# shellcheck disable=SC1091
|
||||
source "$REPO_ROOT/api/.env"
|
||||
set +a
|
||||
fi
|
||||
|
||||
SPECS_JSON="$(mktemp -t dograh-specs-XXXXXX.json)"
|
||||
OPENAPI_JSON="$(mktemp -t dograh-openapi-XXXXXX.json)"
|
||||
trap 'rm -f "$SPECS_JSON" "$OPENAPI_JSON"' EXIT
|
||||
|
||||
# ── 1. Node-spec typed dataclasses ────────────────────────────────────
|
||||
|
||||
echo "→ Dumping node specs from in-process registry..."
|
||||
python -m api.services.workflow.node_specs > "$SPECS_JSON"
|
||||
|
||||
echo "→ Generating Python typed dataclasses..."
|
||||
PYTHONPATH="$REPO_ROOT/sdk/python/src" python -m dograh_sdk.codegen \
|
||||
--input "$SPECS_JSON" \
|
||||
--out "sdk/python/src/dograh_sdk/typed"
|
||||
|
||||
echo "→ Generating TypeScript typed interfaces..."
|
||||
node "sdk/typescript/scripts/codegen.mts" \
|
||||
--input "$SPECS_JSON" \
|
||||
--out "sdk/typescript/src/typed"
|
||||
|
||||
# ── 2. SDK-scoped OpenAPI spec ────────────────────────────────────────
|
||||
|
||||
echo "→ Dumping filtered OpenAPI (sdk_expose routes only)..."
|
||||
python - <<PY
|
||||
import json
|
||||
from loguru import logger
|
||||
logger.remove()
|
||||
from fastapi.openapi.utils import get_openapi
|
||||
from api.app import app
|
||||
|
||||
sdk_routes = [
|
||||
r for r in app.routes
|
||||
if getattr(r, "openapi_extra", None)
|
||||
and "x-sdk-method" in (r.openapi_extra or {})
|
||||
]
|
||||
spec = get_openapi(title=app.title, version=app.version, routes=sdk_routes)
|
||||
with open("$OPENAPI_JSON", "w") as f:
|
||||
json.dump(spec, f)
|
||||
print(f" → {len(sdk_routes)} operations, "
|
||||
f"{len(spec.get('components', {}).get('schemas', {}))} schemas reachable")
|
||||
PY
|
||||
|
||||
# ── 3. Request/response models (off-the-shelf) ────────────────────────
|
||||
|
||||
echo "→ Generating Python Pydantic models (datamodel-codegen)..."
|
||||
datamodel-codegen \
|
||||
--input "$OPENAPI_JSON" \
|
||||
--input-file-type openapi \
|
||||
--output "sdk/python/src/dograh_sdk/_generated_models.py" \
|
||||
--output-model-type pydantic_v2.BaseModel \
|
||||
--target-python-version 3.10 \
|
||||
--use-schema-description \
|
||||
--use-field-description \
|
||||
--use-annotated \
|
||||
--use-union-operator \
|
||||
--field-constraints \
|
||||
--wrap-string-literal
|
||||
|
||||
echo "→ Generating TypeScript types (openapi-typescript)..."
|
||||
if [ ! -d "sdk/typescript/node_modules" ]; then
|
||||
(cd sdk/typescript && npm install --silent)
|
||||
fi
|
||||
(cd sdk/typescript && npx --no-install openapi-typescript \
|
||||
"$OPENAPI_JSON" \
|
||||
--output "src/_generated_models.ts" \
|
||||
--root-types \
|
||||
--root-types-no-schema-prefix)
|
||||
|
||||
# ── 4. Client method mixins ──────────────────────────────────────────
|
||||
|
||||
echo "→ Emitting client method mixins..."
|
||||
python -m sdk.codegen.client_codegen \
|
||||
--input "$OPENAPI_JSON" \
|
||||
--py-out "sdk/python/src/dograh_sdk/_generated_client.py" \
|
||||
--ts-out "sdk/typescript/src/_generated_client.ts"
|
||||
|
||||
echo "✓ SDK regenerated."
|
||||
123
scripts/release_sdks.sh
Executable file
123
scripts/release_sdks.sh
Executable file
|
|
@ -0,0 +1,123 @@
|
|||
#!/usr/bin/env bash
|
||||
# Cut a release of both SDKs — dograh-sdk (PyPI) and @dograh/sdk (npm) —
|
||||
# at the given version. Regenerates typed files from node_specs first so
|
||||
# a stale SDK can't ship.
|
||||
#
|
||||
# Usage:
|
||||
# ./scripts/release_sdks.sh 0.1.2
|
||||
#
|
||||
# Prerequisites (one-time setup):
|
||||
# - `build` + `twine` installed: `pip install --upgrade build twine`
|
||||
# - `npm login` completed as a member of the `dograh` npm org. npm
|
||||
# publish will prompt interactively for a 2FA OTP — run this script
|
||||
# in a terminal where you can type the code.
|
||||
#
|
||||
# The script is idempotent up to the upload steps: each publish is gated
|
||||
# by a y/N prompt, so you can dry-run the build and bail before anything
|
||||
# hits a registry.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VERSION="${1:-}"
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo "usage: $0 <version> # e.g. 0.1.2" >&2
|
||||
exit 1
|
||||
fi
|
||||
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+([.\-][A-Za-z0-9.]+)?$ ]]; then
|
||||
echo "error: '$VERSION' does not look like semver (e.g. 0.1.2 or 0.2.0-rc.1)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
confirm() {
|
||||
local reply
|
||||
read -r -p "$1 [y/N] " reply
|
||||
[[ "$reply" =~ ^[Yy]$ ]]
|
||||
}
|
||||
|
||||
echo "→ Regenerating typed SDK sources from node_specs..."
|
||||
./scripts/generate_sdk.sh
|
||||
|
||||
if ! git diff --quiet -- sdk/python/src/dograh_sdk/typed sdk/typescript/src/typed; then
|
||||
echo
|
||||
echo "⚠ node_specs regeneration changed typed files. Review the diff"
|
||||
echo " above and commit before releasing — otherwise the tag will"
|
||||
echo " point at a tree that disagrees with what ships to the registry."
|
||||
if ! confirm "Continue anyway?"; then
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "→ Bumping versions to $VERSION..."
|
||||
VERSION="$VERSION" python - <<'PY'
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
|
||||
version = os.environ["VERSION"]
|
||||
|
||||
py = pathlib.Path("sdk/python/pyproject.toml")
|
||||
py.write_text(
|
||||
re.sub(r'^version = "[^"]+"', f'version = "{version}"', py.read_text(), count=1, flags=re.M)
|
||||
)
|
||||
|
||||
ts = pathlib.Path("sdk/typescript/package.json")
|
||||
ts.write_text(
|
||||
re.sub(r'"version": "[^"]+"', f'"version": "{version}"', ts.read_text(), count=1)
|
||||
)
|
||||
|
||||
print(f" pyproject.toml → {version}")
|
||||
print(f" package.json → {version}")
|
||||
PY
|
||||
|
||||
echo "→ Building Python wheel + sdist..."
|
||||
(
|
||||
cd sdk/python
|
||||
rm -rf dist build
|
||||
python -m build >/dev/null
|
||||
twine check dist/*
|
||||
)
|
||||
|
||||
echo "→ Building TypeScript + running tests..."
|
||||
(
|
||||
cd sdk/typescript
|
||||
rm -rf dist
|
||||
npm ci --silent
|
||||
npm run build
|
||||
npm test
|
||||
)
|
||||
|
||||
echo
|
||||
echo "============================================================"
|
||||
echo " Built dograh-sdk==$VERSION and @dograh/sdk@$VERSION"
|
||||
echo " Nothing has been published yet."
|
||||
echo "============================================================"
|
||||
echo
|
||||
|
||||
if confirm "Upload dograh-sdk==$VERSION to TestPyPI first (recommended)?"; then
|
||||
(cd sdk/python && twine upload --repository testpypi dist/*)
|
||||
echo " → https://test.pypi.org/project/dograh-sdk/$VERSION/"
|
||||
echo
|
||||
fi
|
||||
|
||||
if confirm "Upload dograh-sdk==$VERSION to PyPI?"; then
|
||||
(cd sdk/python && twine upload dist/*)
|
||||
echo " → https://pypi.org/project/dograh-sdk/$VERSION/"
|
||||
echo
|
||||
fi
|
||||
|
||||
if confirm "Publish @dograh/sdk@$VERSION to npm? (will prompt for 2FA OTP)"; then
|
||||
(cd sdk/typescript && npm publish --access public)
|
||||
echo " → https://www.npmjs.com/package/@dograh/sdk/v/$VERSION"
|
||||
echo
|
||||
fi
|
||||
|
||||
if confirm "Create annotated git tag sdks-v$VERSION at HEAD?"; then
|
||||
git tag -a "sdks-v$VERSION" -m "dograh-sdk + @dograh/sdk $VERSION"
|
||||
echo " → created tag (not pushed). Push with:"
|
||||
echo " git push origin sdks-v$VERSION"
|
||||
fi
|
||||
|
||||
echo "✓ Done."
|
||||
Loading…
Add table
Add a link
Reference in a new issue