From 9551ac1986af7bfa75cb48fa935ebf06521d00ed Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 17 Jun 2025 20:02:47 -0700 Subject: [PATCH 1/4] added ts & python sdk, renamed cli from simstudio to cli --- .github/workflows/publish-cli.yml | 10 +- .github/workflows/publish-python-sdk.yml | 88 ++++++ .github/workflows/publish-ts-sdk.yml | 85 +++++ bun.lock | 109 ++++++- packages/README.md | 249 +++++++++++++++ packages/{simstudio => cli}/README.md | 0 packages/{simstudio => cli}/package.json | 0 packages/{simstudio => cli}/src/index.ts | 0 packages/{simstudio => cli}/tsconfig.json | 0 packages/python-sdk/.gitignore | 85 +++++ packages/python-sdk/README.md | 330 ++++++++++++++++++++ packages/python-sdk/examples/basic_usage.py | 209 +++++++++++++ packages/python-sdk/pyproject.toml | 83 +++++ packages/python-sdk/setup.py | 51 +++ packages/python-sdk/simstudio/__init__.py | 248 +++++++++++++++ packages/python-sdk/tests/__init__.py | 0 packages/python-sdk/tests/test_client.py | 95 ++++++ packages/ts-sdk/.gitignore | 43 +++ packages/ts-sdk/README.md | 275 ++++++++++++++++ packages/ts-sdk/examples/basic-usage.ts | 103 ++++++ packages/ts-sdk/package.json | 53 ++++ packages/ts-sdk/src/index.test.ts | 80 +++++ packages/ts-sdk/src/index.ts | 182 +++++++++++ packages/ts-sdk/tsconfig.json | 20 ++ packages/ts-sdk/vitest.config.ts | 11 + turbo.json | 8 +- 26 files changed, 2403 insertions(+), 14 deletions(-) create mode 100644 .github/workflows/publish-python-sdk.yml create mode 100644 .github/workflows/publish-ts-sdk.yml create mode 100644 packages/README.md rename packages/{simstudio => cli}/README.md (100%) rename packages/{simstudio => cli}/package.json (100%) rename packages/{simstudio => cli}/src/index.ts (100%) rename packages/{simstudio => cli}/tsconfig.json (100%) create mode 100644 packages/python-sdk/.gitignore create mode 100644 packages/python-sdk/README.md create mode 100644 packages/python-sdk/examples/basic_usage.py create mode 100644 packages/python-sdk/pyproject.toml create mode 100644 packages/python-sdk/setup.py create mode 100644 packages/python-sdk/simstudio/__init__.py create mode 100644 packages/python-sdk/tests/__init__.py create mode 100644 packages/python-sdk/tests/test_client.py create mode 100644 packages/ts-sdk/.gitignore create mode 100644 packages/ts-sdk/README.md create mode 100644 packages/ts-sdk/examples/basic-usage.ts create mode 100644 packages/ts-sdk/package.json create mode 100644 packages/ts-sdk/src/index.test.ts create mode 100644 packages/ts-sdk/src/index.ts create mode 100644 packages/ts-sdk/tsconfig.json create mode 100644 packages/ts-sdk/vitest.config.ts diff --git a/.github/workflows/publish-cli.yml b/.github/workflows/publish-cli.yml index 81b1b6893df..41bce6a7b68 100644 --- a/.github/workflows/publish-cli.yml +++ b/.github/workflows/publish-cli.yml @@ -4,7 +4,7 @@ on: push: branches: [main] paths: - - 'packages/simstudio/**' + - 'packages/cli/**' jobs: publish-npm: @@ -25,16 +25,16 @@ jobs: registry-url: 'https://registry.npmjs.org/' - name: Install dependencies - working-directory: packages/simstudio + working-directory: packages/cli run: bun install - name: Build package - working-directory: packages/simstudio + working-directory: packages/cli run: bun run build - name: Get package version id: package_version - working-directory: packages/simstudio + working-directory: packages/cli run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT - name: Check if version already exists @@ -48,7 +48,7 @@ jobs: - name: Publish to npm if: steps.version_check.outputs.exists == 'false' - working-directory: packages/simstudio + working-directory: packages/cli run: npm publish --access=public env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/publish-python-sdk.yml b/.github/workflows/publish-python-sdk.yml new file mode 100644 index 00000000000..bd5bdebfdd5 --- /dev/null +++ b/.github/workflows/publish-python-sdk.yml @@ -0,0 +1,88 @@ +name: Publish Python SDK + +on: + push: + branches: [main] + paths: + - 'packages/python-sdk/**' + +jobs: + publish-pypi: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install build dependencies + run: | + python -m pip install --upgrade pip + pip install build twine pytest requests + + - name: Run tests + working-directory: packages/python-sdk + run: | + PYTHONPATH=. pytest tests/ -v + + - name: Get package version + id: package_version + working-directory: packages/python-sdk + run: echo "version=$(python -c "import re; print(re.search(r'version = \"(.+?)\"', open('pyproject.toml').read()).group(1))")" >> $GITHUB_OUTPUT + + - name: Check if version already exists + id: version_check + run: | + if pip index versions simstudio-sdk | grep -q "${{ steps.package_version.outputs.version }}"; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - name: Build package + if: steps.version_check.outputs.exists == 'false' + working-directory: packages/python-sdk + run: python -m build + + - name: Check package + if: steps.version_check.outputs.exists == 'false' + working-directory: packages/python-sdk + run: twine check dist/* + + - name: Publish to PyPI + if: steps.version_check.outputs.exists == 'false' + working-directory: packages/python-sdk + env: + TWINE_USERNAME: __token__ + TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} + run: twine upload dist/* + + - name: Log skipped publish + if: steps.version_check.outputs.exists == 'true' + run: echo "Skipped publishing because version ${{ steps.package_version.outputs.version }} already exists on PyPI" + + - name: Create GitHub Release + if: steps.version_check.outputs.exists == 'false' + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: python-sdk-v${{ steps.package_version.outputs.version }} + release_name: Python SDK v${{ steps.package_version.outputs.version }} + body: | + ## Python SDK v${{ steps.package_version.outputs.version }} + + Published simstudio-sdk==${{ steps.package_version.outputs.version }} to PyPI. + + ### Installation + ```bash + pip install simstudio-sdk==${{ steps.package_version.outputs.version }} + ``` + + ### Documentation + See the [README](https://github.com/simstudio/sim/tree/main/packages/python-sdk) for usage instructions. + draft: false + prerelease: false \ No newline at end of file diff --git a/.github/workflows/publish-ts-sdk.yml b/.github/workflows/publish-ts-sdk.yml new file mode 100644 index 00000000000..2d0b4316297 --- /dev/null +++ b/.github/workflows/publish-ts-sdk.yml @@ -0,0 +1,85 @@ +name: Publish TypeScript SDK + +on: + push: + branches: [main] + paths: + - 'packages/ts-sdk/**' + +jobs: + publish-npm: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Setup Node.js for npm publishing + uses: actions/setup-node@v4 + with: + node-version: '20' + registry-url: 'https://registry.npmjs.org/' + + - name: Install dependencies + working-directory: packages/ts-sdk + run: bun install + + - name: Run tests + working-directory: packages/ts-sdk + run: bun run test + + - name: Build package + working-directory: packages/ts-sdk + run: bun run build + + - name: Get package version + id: package_version + working-directory: packages/ts-sdk + run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT + + - name: Check if version already exists + id: version_check + run: | + if npm view simstudio-ts-sdk@${{ steps.package_version.outputs.version }} version &> /dev/null; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - name: Publish to npm + if: steps.version_check.outputs.exists == 'false' + working-directory: packages/ts-sdk + run: npm publish --access=public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Log skipped publish + if: steps.version_check.outputs.exists == 'true' + run: echo "Skipped publishing because version ${{ steps.package_version.outputs.version }} already exists on npm" + + - name: Create GitHub Release + if: steps.version_check.outputs.exists == 'false' + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: typescript-sdk-v${{ steps.package_version.outputs.version }} + release_name: TypeScript SDK v${{ steps.package_version.outputs.version }} + body: | + ## TypeScript SDK v${{ steps.package_version.outputs.version }} + + Published simstudio-ts-sdk@${{ steps.package_version.outputs.version }} to npm. + + ### Installation + ```bash + npm install simstudio-ts-sdk@${{ steps.package_version.outputs.version }} + ``` + + ### Documentation + See the [README](https://github.com/simstudio/sim/tree/main/packages/ts-sdk) for usage instructions. + draft: false + prerelease: false \ No newline at end of file diff --git a/bun.lock b/bun.lock index 9383302b81f..9be91bb90d7 100644 --- a/bun.lock +++ b/bun.lock @@ -168,7 +168,7 @@ "vitest": "^3.0.8", }, }, - "packages/simstudio": { + "packages/cli": { "name": "simstudio", "version": "0.1.18", "bin": { @@ -187,6 +187,19 @@ "typescript": "^5.1.6", }, }, + "packages/ts-sdk": { + "name": "simstudio-ts-sdk", + "version": "0.1.0", + "dependencies": { + "node-fetch": "^3.3.2", + }, + "devDependencies": { + "@types/node": "^20.5.1", + "@vitest/coverage-v8": "^3.0.8", + "typescript": "^5.1.6", + "vitest": "^3.0.8", + }, + }, }, "trustedDependencies": [ "sharp", @@ -1697,6 +1710,8 @@ "dat.gui": ["dat.gui@0.7.9", "", {}, "sha512-sCNc1OHobc+Erc1HqiswYgHdVNpSJUlk/Hz8vzOCsER7rl+oF/4+v8GXFUyCgtXpoCX6+bnmg07DedLvBLwYKQ=="], + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], + "data-urls": ["data-urls@5.0.0", "", { "dependencies": { "whatwg-mimetype": "^4.0.0", "whatwg-url": "^14.0.0" } }, "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg=="], "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], @@ -1879,6 +1894,8 @@ "fdir": ["fdir@6.4.5", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw=="], + "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="], + "fflate": ["fflate@0.8.2", "", {}, "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A=="], "figures": ["figures@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg=="], @@ -1895,6 +1912,8 @@ "formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="], + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], + "forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="], "frac": ["frac@1.1.2", "", {}, "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA=="], @@ -2393,7 +2412,7 @@ "node-ensure": ["node-ensure@0.0.0", "", {}, "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw=="], - "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="], @@ -2753,7 +2772,9 @@ "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], - "simstudio": ["simstudio@workspace:packages/simstudio"], + "simstudio": ["simstudio@workspace:packages/cli"], + + "simstudio-ts-sdk": ["simstudio-ts-sdk@workspace:packages/ts-sdk"], "slice-ansi": ["slice-ansi@5.0.0", "", { "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" } }, "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ=="], @@ -3063,6 +3084,8 @@ "@anthropic-ai/sdk/@types/node": ["@types/node@18.19.110", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q=="], + "@anthropic-ai/sdk/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "@asamuzakjp/css-color/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "@aws-crypto/sha1-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=="], @@ -3083,8 +3106,12 @@ "@browserbasehq/sdk/@types/node": ["@types/node@18.19.110", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q=="], + "@browserbasehq/sdk/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "@cerebras/cerebras_cloud_sdk/@types/node": ["@types/node@18.19.110", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q=="], + "@cerebras/cerebras_cloud_sdk/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], "@grpc/proto-loader/long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="], @@ -3301,6 +3328,8 @@ "@sentry/cli/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + "@sentry/cli/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "@sentry/nextjs/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], "@sentry/nextjs/resolve": ["resolve@1.22.8", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw=="], @@ -3385,6 +3414,8 @@ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + "fetch-blob/web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="], + "form-data/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], "fumadocs-mdx/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], @@ -3393,6 +3424,8 @@ "fumadocs-ui/postcss-selector-parser": ["postcss-selector-parser@7.1.0", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA=="], + "gaxios/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "gaxios/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], "glob/minimatch": ["minimatch@10.0.1", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ=="], @@ -3401,12 +3434,16 @@ "groq-sdk/@types/node": ["@types/node@18.19.110", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q=="], + "groq-sdk/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="], "htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "inquirer/ora": ["ora@5.4.1", "", { "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", "cli-cursor": "^3.1.0", "cli-spinners": "^2.5.0", "is-interactive": "^1.0.0", "is-unicode-supported": "^0.1.0", "log-symbols": "^4.1.0", "strip-ansi": "^6.0.0", "wcwidth": "^1.0.1" } }, "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ=="], + "isomorphic-unfetch/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "jaeger-client/uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="], "jest-diff/pretty-format": ["pretty-format@26.6.2", "", { "dependencies": { "@jest/types": "^26.6.2", "ansi-regex": "^5.0.0", "ansi-styles": "^4.0.0", "react-is": "^17.0.1" } }, "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg=="], @@ -3451,14 +3488,16 @@ "next-runtime-env/next": ["next@14.2.29", "", { "dependencies": { "@next/env": "14.2.29", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "graceful-fs": "^4.2.11", "postcss": "8.4.31", "styled-jsx": "5.1.1" }, "optionalDependencies": { "@next/swc-darwin-arm64": "14.2.29", "@next/swc-darwin-x64": "14.2.29", "@next/swc-linux-arm64-gnu": "14.2.29", "@next/swc-linux-arm64-musl": "14.2.29", "@next/swc-linux-x64-gnu": "14.2.29", "@next/swc-linux-x64-musl": "14.2.29", "@next/swc-win32-arm64-msvc": "14.2.29", "@next/swc-win32-ia32-msvc": "14.2.29", "@next/swc-win32-x64-msvc": "14.2.29" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-s98mCOMOWLGGpGOfgKSnleXLuegvvH415qtRZXpSp00HeEgdmrxmwL9cgKU+h4XrhB16zEI5d/7BnkS3ATInsA=="], - "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - "openai/@types/node": ["@types/node@18.19.110", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-WW2o4gTmREtSnqKty9nhqF/vA0GKd0V/rbC0OyjSk9Bz6bzlsXKT+i7WDdS/a0z74rfT2PO4dArVCSnapNLA5Q=="], + "openai/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "openapi/commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="], "openapi/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], + "openapi/node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], + "openapi/object-hash": ["object-hash@2.2.0", "", {}, "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="], "ora/chalk": ["chalk@5.4.1", "", {}, "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w=="], @@ -3505,6 +3544,8 @@ "simstudio/@types/node": ["@types/node@20.17.57", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ=="], + "simstudio-ts-sdk/@types/node": ["@types/node@20.17.57", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-f3T4y6VU4fVQDKVqJV4Uppy8c1p/sVvS3peyqxyWnzkqXFJLRU7Y1Bl7rMS1Qe9z0v4M6McY0Fp9yBsgHJUsWQ=="], + "slice-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="], "slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@4.0.0", "", {}, "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ=="], @@ -3541,6 +3582,8 @@ "@anthropic-ai/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "@anthropic-ai/sdk/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "@aws-crypto/sha1-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-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=="], @@ -3553,8 +3596,12 @@ "@browserbasehq/sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "@browserbasehq/sdk/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "@cerebras/cerebras_cloud_sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "@cerebras/cerebras_cloud_sdk/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="], "@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="], @@ -3685,6 +3732,8 @@ "@sentry/cli/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + "@sentry/cli/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "@sentry/node/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.28.0", "", {}, "sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA=="], "@sentry/node/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.57.2", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-uIX52NnTM0iBh84MShlpouI7UKqkZ7MrUszTmaypHBu4r7NofznSnQRfJ+uUeDtQDj6w8eFGg5KBLDAwAPz1+A=="], @@ -3707,14 +3756,20 @@ "fumadocs-mdx/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "gaxios/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "groq-sdk/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "groq-sdk/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "inquirer/ora/is-interactive": ["is-interactive@1.0.0", "", {}, "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="], "inquirer/ora/is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="], "inquirer/ora/log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="], + "isomorphic-unfetch/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "jest-diff/pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], "lint-staged/listr2/cli-truncate": ["cli-truncate@4.0.0", "", { "dependencies": { "slice-ansi": "^5.0.0", "string-width": "^7.0.0" } }, "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA=="], @@ -3765,11 +3820,11 @@ "next-runtime-env/next/styled-jsx": ["styled-jsx@5.1.1", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" } }, "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw=="], - "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + "openai/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], - "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "openai/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], - "openai/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], + "openapi/node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], "ora/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], @@ -3779,6 +3834,8 @@ "react-email/chokidar/readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + "simstudio-ts-sdk/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], + "simstudio/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], "sucrase/glob/jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="], @@ -3791,20 +3848,48 @@ "webpack/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "@anthropic-ai/sdk/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "@anthropic-ai/sdk/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "@aws-crypto/sha1-browser/@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-crypto/sha256-browser/@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-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=="], + "@browserbasehq/sdk/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "@browserbasehq/sdk/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "@cerebras/cerebras_cloud_sdk/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "@cerebras/cerebras_cloud_sdk/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "@sentry/bundler-plugin-core/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "@sentry/bundler-plugin-core/glob/path-scurry/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="], + "@sentry/cli/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "@sentry/cli/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "bl/readable-stream/string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], "cli-truncate/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], + "gaxios/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "gaxios/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "groq-sdk/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "groq-sdk/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "isomorphic-unfetch/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "isomorphic-unfetch/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "lint-staged/listr2/cli-truncate/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "lint-staged/listr2/log-update/ansi-escapes": ["ansi-escapes@7.0.0", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw=="], @@ -3829,6 +3914,14 @@ "log-update/wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "openai/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "openai/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + + "openapi/node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "openapi/node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "ora/cli-cursor/restore-cursor/onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], "sucrase/glob/path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], diff --git a/packages/README.md b/packages/README.md new file mode 100644 index 00000000000..3076db2434b --- /dev/null +++ b/packages/README.md @@ -0,0 +1,249 @@ +# Sim Studio SDKs + +This directory contains the official SDKs for [Sim Studio](https://simstudio.ai), allowing developers to execute workflows programmatically from their applications. + +## Available SDKs + +### Package Installation Commands + +- **TypeScript/JavaScript**: `npm install simstudio-ts-sdk` +- **Python**: `pip install simstudio-sdk` + +### 🟢 TypeScript/JavaScript SDK (`simstudio-ts-sdk`) + +**Directory:** `ts-sdk/` + +The TypeScript SDK provides type-safe workflow execution for Node.js and browser environments. + +**Installation:** +```bash +npm install simstudio-ts-sdk +# or +yarn add simstudio-ts-sdk +# or +bun add simstudio-ts-sdk +``` + +**Quick Start:** +```typescript +import { SimStudioClient } from 'simstudio-ts-sdk'; + +const client = new SimStudioClient({ + apiKey: 'your-api-key-here' +}); + +const result = await client.executeWorkflow('workflow-id', { + input: { message: 'Hello, world!' } +}); +``` + +### šŸ Python SDK (`simstudio-sdk`) + +**Directory:** `python-sdk/` + +The Python SDK provides Pythonic workflow execution with comprehensive error handling and data classes. + +**Installation:** +```bash +pip install simstudio-sdk +``` + +**Quick Start:** +```python +from simstudio import SimStudioClient + +client = SimStudioClient(api_key='your-api-key-here') + +result = client.execute_workflow('workflow-id', + input_data={'message': 'Hello, world!'}) +``` + +## Core Features + +Both SDKs provide the same core functionality: + +āœ… **Workflow Execution** - Execute deployed workflows with optional input data +āœ… **Status Checking** - Check deployment status and workflow readiness +āœ… **Error Handling** - Comprehensive error handling with specific error codes +āœ… **Timeout Support** - Configurable timeouts for workflow execution +āœ… **Input Validation** - Validate workflows before execution +āœ… **Type Safety** - Full type definitions (TypeScript) and data classes (Python) + +## API Compatibility + +Both SDKs are built on top of the same REST API endpoints: + +- `POST /api/workflows/{id}/execute` - Execute workflow with input +- `GET /api/workflows/{id}/execute` - Execute workflow without input +- `GET /api/workflows/{id}/status` - Get workflow status + +## Authentication + +Both SDKs use API key authentication via the `X-API-Key` header. You can obtain an API key by: + +1. Logging in to your [Sim Studio](https://simstudio.ai) account +2. Navigating to your workflow +3. Clicking "Deploy" to deploy your workflow +4. Creating or selecting an API key during deployment + +## Environment Variables + +Both SDKs support environment variable configuration: + +```bash +# Required +SIMSTUDIO_API_KEY=your-api-key-here + +# Optional +SIMSTUDIO_BASE_URL=https://simstudio.ai # or your custom domain +``` + +## Error Handling + +Both SDKs provide consistent error handling with these error codes: + +| Code | Description | +|------|-------------| +| `UNAUTHORIZED` | Invalid API key | +| `TIMEOUT` | Request timed out | +| `USAGE_LIMIT_EXCEEDED` | Account usage limit exceeded | +| `EXECUTION_ERROR` | General execution error | +| `STATUS_ERROR` | Error getting workflow status | + +## Examples + +### TypeScript Example + +```typescript +import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk'; + +const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY! +}); + +try { + // Check if workflow is ready + const isReady = await client.validateWorkflow('workflow-id'); + if (!isReady) { + throw new Error('Workflow not deployed'); + } + + // Execute workflow + const result = await client.executeWorkflow('workflow-id', { + input: { data: 'example' }, + timeout: 30000 + }); + + if (result.success) { + console.log('Output:', result.output); + } +} catch (error) { + if (error instanceof SimStudioError) { + console.error(`Error ${error.code}: ${error.message}`); + } +} +``` + +### Python Example + +```python +from simstudio import SimStudioClient, SimStudioError +import os + +client = SimStudioClient(api_key=os.getenv('SIMSTUDIO_API_KEY')) + +try: + # Check if workflow is ready + is_ready = client.validate_workflow('workflow-id') + if not is_ready: + raise Exception('Workflow not deployed') + + # Execute workflow + result = client.execute_workflow('workflow-id', + input_data={'data': 'example'}, + timeout=30.0) + + if result.success: + print(f'Output: {result.output}') + +except SimStudioError as error: + print(f'Error {error.code}: {error}') +``` + +## Development + +### Building the SDKs + +**TypeScript SDK:** +```bash +cd packages/ts-sdk +bun install +bun run build +``` + +**Python SDK:** +```bash +cd packages/python-sdk +pip install -e ".[dev]" +python -m build +``` + +### Running Examples + +**TypeScript:** +```bash +cd packages/ts-sdk +SIMSTUDIO_API_KEY=your-key bun run examples/basic-usage.ts +``` + +**Python:** +```bash +cd packages/python-sdk +SIMSTUDIO_API_KEY=your-key python examples/basic_usage.py +``` + +### Testing + +**TypeScript:** +```bash +cd packages/ts-sdk +bun run test +``` + +**Python:** +```bash +cd packages/python-sdk +pytest +``` + +## Publishing + +The SDKs are automatically published to npm and PyPI when changes are pushed to the main branch. See [Publishing Setup](../.github/PUBLISHING.md) for details on: + +- Setting up GitHub secrets for automated publishing +- Manual publishing instructions +- Version management and semantic versioning +- Troubleshooting common issues + +## Contributing + +1. Fork the repository +2. Create a feature branch: `git checkout -b feature/amazing-feature` +3. Make your changes +4. Add tests for your changes +5. Run the test suite: `bun run test` (TypeScript) or `pytest` (Python) +6. Update version numbers if needed +7. Commit your changes: `git commit -m 'Add amazing feature'` +8. Push to the branch: `git push origin feature/amazing-feature` +9. Open a Pull Request + +## License + +Both SDKs are licensed under the Apache-2.0 License. See the [LICENSE](../LICENSE) file for details. + +## Support + +- šŸ“– [Documentation](https://docs.simstudio.ai) +- šŸ’¬ [Discord Community](https://discord.gg/simstudio) +- šŸ› [Issue Tracker](https://github.com/simstudioai/sim/issues) +- šŸ“§ [Email Support](mailto:support@simstudio.ai) \ No newline at end of file diff --git a/packages/simstudio/README.md b/packages/cli/README.md similarity index 100% rename from packages/simstudio/README.md rename to packages/cli/README.md diff --git a/packages/simstudio/package.json b/packages/cli/package.json similarity index 100% rename from packages/simstudio/package.json rename to packages/cli/package.json diff --git a/packages/simstudio/src/index.ts b/packages/cli/src/index.ts similarity index 100% rename from packages/simstudio/src/index.ts rename to packages/cli/src/index.ts diff --git a/packages/simstudio/tsconfig.json b/packages/cli/tsconfig.json similarity index 100% rename from packages/simstudio/tsconfig.json rename to packages/cli/tsconfig.json diff --git a/packages/python-sdk/.gitignore b/packages/python-sdk/.gitignore new file mode 100644 index 00000000000..db85d8fa26b --- /dev/null +++ b/packages/python-sdk/.gitignore @@ -0,0 +1,85 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Virtual environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +test_env/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# Environments +.env +.env.local + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json \ No newline at end of file diff --git a/packages/python-sdk/README.md b/packages/python-sdk/README.md new file mode 100644 index 00000000000..ec5689908ff --- /dev/null +++ b/packages/python-sdk/README.md @@ -0,0 +1,330 @@ +# Sim Studio Python SDK + +The official Python SDK for [Sim Studio](https://simstudio.ai), allowing you to execute workflows programmatically from your Python applications. + +## Installation + +```bash +pip install simstudio-sdk +``` + +## Quick Start + +```python +from simstudio import SimStudioClient + +# Initialize the client +client = SimStudioClient( + api_key="your-api-key-here", + base_url="https://simstudio.ai" # optional, defaults to https://simstudio.ai +) + +# Execute a workflow +try: + result = client.execute_workflow("workflow-id") + print("Workflow executed successfully:", result) +except Exception as error: + print("Workflow execution failed:", error) +``` + +## API Reference + +### SimStudioClient + +#### Constructor + +```python +SimStudioClient(api_key: str, base_url: str = "https://simstudio.ai") +``` + +- `api_key` (str): Your Sim Studio API key +- `base_url` (str, optional): Base URL for the Sim Studio API (defaults to `https://simstudio.ai`) + +#### Methods + +##### execute_workflow(workflow_id, input_data=None, timeout=30.0) + +Execute a workflow with optional input data. + +```python +result = client.execute_workflow( + "workflow-id", + input_data={"message": "Hello, world!"}, + timeout=30.0 # 30 seconds +) +``` + +**Parameters:** +- `workflow_id` (str): The ID of the workflow to execute +- `input_data` (dict, optional): Input data to pass to the workflow +- `timeout` (float): Timeout in seconds (default: 30.0) + +**Returns:** `WorkflowExecutionResult` + +##### get_workflow_status(workflow_id) + +Get the status of a workflow (deployment status, etc.). + +```python +status = client.get_workflow_status("workflow-id") +print("Is deployed:", status.is_deployed) +``` + +**Parameters:** +- `workflow_id` (str): The ID of the workflow + +**Returns:** `WorkflowStatus` + +##### validate_workflow(workflow_id) + +Validate that a workflow is ready for execution. + +```python +is_ready = client.validate_workflow("workflow-id") +if is_ready: + # Workflow is deployed and ready + pass +``` + +**Parameters:** +- `workflow_id` (str): The ID of the workflow + +**Returns:** `bool` + +##### execute_workflow_sync(workflow_id, input_data=None, timeout=30.0, poll_interval=1.0, max_wait_time=300.0) + +Execute a workflow and poll for completion (useful for long-running workflows). + +```python +result = client.execute_workflow_sync( + "workflow-id", + input_data={"data": "some input"}, + timeout=60.0, + poll_interval=2.0, + max_wait_time=300.0 +) +``` + +**Parameters:** +- `workflow_id` (str): The ID of the workflow to execute +- `input_data` (dict, optional): Input data to pass to the workflow +- `timeout` (float): Timeout for the initial request in seconds +- `poll_interval` (float): Polling interval in seconds (default: 1.0) +- `max_wait_time` (float): Maximum wait time in seconds (default: 300.0) + +**Returns:** `WorkflowExecutionResult` + +##### set_api_key(api_key) + +Update the API key. + +```python +client.set_api_key("new-api-key") +``` + +##### set_base_url(base_url) + +Update the base URL. + +```python +client.set_base_url("https://my-custom-domain.com") +``` + +##### close() + +Close the underlying HTTP session. + +```python +client.close() +``` + +## Data Classes + +### WorkflowExecutionResult + +```python +@dataclass +class WorkflowExecutionResult: + success: bool + output: Optional[Any] = None + error: Optional[str] = None + logs: Optional[list] = None + metadata: Optional[Dict[str, Any]] = None + trace_spans: Optional[list] = None + total_duration: Optional[float] = None +``` + +### WorkflowStatus + +```python +@dataclass +class WorkflowStatus: + is_deployed: bool + deployed_at: Optional[str] = None + is_published: bool = False + needs_redeployment: bool = False +``` + +### SimStudioError + +```python +class SimStudioError(Exception): + def __init__(self, message: str, code: Optional[str] = None, status: Optional[int] = None): + self.code = code + self.status = status +``` + +## Examples + +### Basic Workflow Execution + +```python +import os +from simstudio import SimStudioClient + +client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) + +def run_workflow(): + try: + # Check if workflow is ready + is_ready = client.validate_workflow("my-workflow-id") + if not is_ready: + raise Exception("Workflow is not deployed or ready") + + # Execute the workflow + result = client.execute_workflow( + "my-workflow-id", + input_data={ + "message": "Process this data", + "user_id": "12345" + } + ) + + if result.success: + print("Output:", result.output) + print("Duration:", result.metadata.get("duration") if result.metadata else None) + else: + print("Workflow failed:", result.error) + + except Exception as error: + print("Error:", error) + +run_workflow() +``` + +### Error Handling + +```python +from simstudio import SimStudioClient, SimStudioError +import os + +client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) + +def execute_with_error_handling(): + try: + result = client.execute_workflow("workflow-id") + return result + except SimStudioError as error: + if error.code == "UNAUTHORIZED": + print("Invalid API key") + elif error.code == "TIMEOUT": + print("Workflow execution timed out") + elif error.code == "USAGE_LIMIT_EXCEEDED": + print("Usage limit exceeded") + else: + print(f"Workflow error: {error}") + raise + except Exception as error: + print(f"Unexpected error: {error}") + raise +``` + +### Context Manager Usage + +```python +from simstudio import SimStudioClient +import os + +# Using context manager to automatically close the session +with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client: + result = client.execute_workflow("workflow-id") + print("Result:", result) +# Session is automatically closed here +``` + +### Environment Configuration + +```python +import os +from simstudio import SimStudioClient + +# Using environment variables +client = SimStudioClient( + api_key=os.getenv("SIMSTUDIO_API_KEY"), + base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://simstudio.ai") +) +``` + +### Batch Workflow Execution + +```python +from simstudio import SimStudioClient +import os + +client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) + +def execute_workflows_batch(workflow_data_pairs): + """Execute multiple workflows with different input data.""" + results = [] + + for workflow_id, input_data in workflow_data_pairs: + try: + # Validate workflow before execution + if not client.validate_workflow(workflow_id): + print(f"Skipping {workflow_id}: not deployed") + continue + + result = client.execute_workflow(workflow_id, input_data) + results.append({ + "workflow_id": workflow_id, + "success": result.success, + "output": result.output, + "error": result.error + }) + + except Exception as error: + results.append({ + "workflow_id": workflow_id, + "success": False, + "error": str(error) + }) + + return results + +# Example usage +workflows = [ + ("workflow-1", {"type": "analysis", "data": "sample1"}), + ("workflow-2", {"type": "processing", "data": "sample2"}), +] + +results = execute_workflows_batch(workflows) +for result in results: + print(f"Workflow {result['workflow_id']}: {'Success' if result['success'] else 'Failed'}") +``` + +## Getting Your API Key + +1. Log in to your [Sim Studio](https://simstudio.ai) account +2. Navigate to your workflow +3. Click on "Deploy" to deploy your workflow +4. Select or create an API key during the deployment process +5. Copy the API key to use in your application + +## Requirements + +- Python 3.8+ +- requests >= 2.25.0 + +## License + +Apache-2.0 \ No newline at end of file diff --git a/packages/python-sdk/examples/basic_usage.py b/packages/python-sdk/examples/basic_usage.py new file mode 100644 index 00000000000..89bcb871780 --- /dev/null +++ b/packages/python-sdk/examples/basic_usage.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +""" +Basic usage examples for the Sim Studio Python SDK +""" + +import os +from simstudio import SimStudioClient, SimStudioError + + +def basic_example(): + """Example 1: Basic workflow execution""" + client = SimStudioClient( + api_key=os.getenv("SIMSTUDIO_API_KEY"), + base_url="https://simstudio.ai" + ) + + try: + # Execute a workflow without input + result = client.execute_workflow("your-workflow-id") + + if result.success: + print("āœ… Workflow executed successfully!") + print(f"Output: {result.output}") + if result.metadata: + print(f"Duration: {result.metadata.get('duration')} ms") + else: + print(f"āŒ Workflow failed: {result.error}") + + except SimStudioError as error: + print(f"SDK Error: {error} (Code: {error.code})") + except Exception as error: + print(f"Unexpected error: {error}") + + +def with_input_example(): + """Example 2: Workflow execution with input data""" + client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) + + try: + result = client.execute_workflow( + "your-workflow-id", + input_data={ + "message": "Hello from Python SDK!", + "user_id": "12345", + "data": { + "type": "analysis", + "parameters": { + "include_metadata": True, + "format": "json" + } + } + }, + timeout=60.0 # 60 seconds + ) + + print(f"Execution result: {result}") + + except Exception as error: + print(f"Error: {error}") + + +def status_example(): + """Example 3: Workflow validation and status checking""" + client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) + + try: + # Check if workflow is ready + is_ready = client.validate_workflow("your-workflow-id") + print(f"Workflow ready: {is_ready}") + + # Get detailed status + status = client.get_workflow_status("your-workflow-id") + print(f"Status: {{\n" + f" deployed: {status.is_deployed},\n" + f" published: {status.is_published},\n" + f" needs_redeployment: {status.needs_redeployment},\n" + f" deployed_at: {status.deployed_at}\n" + f"}}") + + if status.is_deployed: + # Execute the workflow + result = client.execute_workflow("your-workflow-id") + print(f"Result: {result}") + + except Exception as error: + print(f"Error: {error}") + + +def context_manager_example(): + """Example 4: Using context manager""" + with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client: + try: + result = client.execute_workflow("your-workflow-id") + print(f"Result: {result}") + except Exception as error: + print(f"Error: {error}") + # Session is automatically closed here + + +def batch_execution_example(): + """Example 5: Batch workflow execution""" + client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) + + workflows = [ + ("workflow-1", {"type": "analysis", "data": "sample1"}), + ("workflow-2", {"type": "processing", "data": "sample2"}), + ("workflow-3", {"type": "validation", "data": "sample3"}), + ] + + results = [] + + for workflow_id, input_data in workflows: + try: + # Validate workflow before execution + if not client.validate_workflow(workflow_id): + print(f"āš ļø Skipping {workflow_id}: not deployed") + continue + + result = client.execute_workflow(workflow_id, input_data) + results.append({ + "workflow_id": workflow_id, + "success": result.success, + "output": result.output, + "error": result.error + }) + + status = "āœ… Success" if result.success else "āŒ Failed" + print(f"{status}: {workflow_id}") + + except Exception as error: + results.append({ + "workflow_id": workflow_id, + "success": False, + "error": str(error) + }) + print(f"āŒ Error in {workflow_id}: {error}") + + # Summary + successful = sum(1 for r in results if r["success"]) + total = len(results) + print(f"\nšŸ“Š Summary: {successful}/{total} workflows completed successfully") + + return results + + +def error_handling_example(): + """Example 6: Comprehensive error handling""" + client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) + + try: + result = client.execute_workflow("your-workflow-id") + return result + except SimStudioError as error: + if error.code == "UNAUTHORIZED": + print("āŒ Invalid API key") + elif error.code == "TIMEOUT": + print("ā±ļø Workflow execution timed out") + elif error.code == "USAGE_LIMIT_EXCEEDED": + print("šŸ’³ Usage limit exceeded") + elif error.status == 404: + print("šŸ” Workflow not found") + elif error.status == 403: + print("🚫 Workflow is not deployed") + else: + print(f"āš ļø Workflow error: {error}") + raise + except Exception as error: + print(f"šŸ’„ Unexpected error: {error}") + raise + + +if __name__ == "__main__": + print("šŸš€ Running Sim Studio Python SDK Examples\n") + + # Check if API key is set + if not os.getenv("SIMSTUDIO_API_KEY"): + print("āŒ Please set SIMSTUDIO_API_KEY environment variable") + exit(1) + + try: + print("1ļøāƒ£ Basic Example:") + basic_example() + print("\nāœ… Basic example completed\n") + + print("2ļøāƒ£ Input Example:") + with_input_example() + print("\nāœ… Input example completed\n") + + print("3ļøāƒ£ Status Example:") + status_example() + print("\nāœ… Status example completed\n") + + print("4ļøāƒ£ Context Manager Example:") + context_manager_example() + print("\nāœ… Context manager example completed\n") + + print("5ļøāƒ£ Batch Execution Example:") + batch_execution_example() + print("\nāœ… Batch execution example completed\n") + + print("6ļøāƒ£ Error Handling Example:") + error_handling_example() + print("\nāœ… Error handling example completed\n") + + except Exception as e: + print(f"\nšŸ’„ Example failed: {e}") + exit(1) + + print("šŸŽ‰ All examples completed successfully!") \ No newline at end of file diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml new file mode 100644 index 00000000000..1b725b404d5 --- /dev/null +++ b/packages/python-sdk/pyproject.toml @@ -0,0 +1,83 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "simstudio-sdk" +version = "0.1.0" +authors = [ + {name = "Sim Studio", email = "support@simstudio.ai"}, +] +description = "Sim Studio SDK - Execute workflows programmatically" +readme = "README.md" +license = {text = "Apache-2.0"} +requires-python = ">=3.8" +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Internet :: WWW/HTTP :: HTTP Servers", + "Topic :: Scientific/Engineering :: Artificial Intelligence", +] +keywords = ["simstudio", "ai", "workflow", "sdk", "api", "automation"] +dependencies = [ + "requests>=2.25.0", + "typing-extensions>=4.0.0; python_version<'3.10'", +] + +[project.optional-dependencies] +dev = [ + "pytest>=6.0.0", + "pytest-asyncio>=0.18.0", + "black>=22.0.0", + "flake8>=4.0.0", + "mypy>=0.910", + "isort>=5.0.0", +] + +[project.urls] +Homepage = "https://simstudio.ai" +Documentation = "https://docs.simstudio.ai" +Repository = "https://github.com/simstudioai/sim" +"Bug Reports" = "https://github.com/simstudioai/sim/issues" + +[tool.setuptools.packages.find] +where = ["."] +include = ["simstudio*"] + +[tool.black] +line-length = 100 +target-version = ['py38', 'py39', 'py310', 'py311', 'py312'] + +[tool.isort] +profile = "black" +line_length = 100 + +[tool.mypy] +python_version = "3.8" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = true +disallow_incomplete_defs = true +check_untyped_defs = true +disallow_untyped_decorators = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +warn_unreachable = true +strict_equality = true + +[tool.pytest.ini_options] +testpaths = ["tests"] +python_files = ["test_*.py"] +python_classes = ["Test*"] +python_functions = ["test_*"] \ No newline at end of file diff --git a/packages/python-sdk/setup.py b/packages/python-sdk/setup.py new file mode 100644 index 00000000000..64117ffc6b7 --- /dev/null +++ b/packages/python-sdk/setup.py @@ -0,0 +1,51 @@ +from setuptools import setup, find_packages + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setup( + name="simstudio-sdk", + version="0.1.0", + author="Sim Studio", + author_email="support@simstudio.ai", + description="Sim Studio SDK - Execute workflows programmatically", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/simstudioai/sim", + packages=find_packages(), + classifiers=[ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + ], + python_requires=">=3.8", + install_requires=[ + "requests>=2.25.0", + "typing-extensions>=4.0.0; python_version<'3.10'", + ], + extras_require={ + "dev": [ + "pytest>=6.0.0", + "pytest-asyncio>=0.18.0", + "black>=22.0.0", + "flake8>=4.0.0", + "mypy>=0.910", + ], + "test": [ + "pytest>=6.0.0", + ], + }, + keywords=["simstudio", "ai", "workflow", "sdk", "api", "automation"], + project_urls={ + "Bug Reports": "https://github.com/simstudioai/sim/issues", + "Source": "https://github.com/simstudioai/sim", + "Documentation": "https://docs.simstudio.ai", + }, +) \ No newline at end of file diff --git a/packages/python-sdk/simstudio/__init__.py b/packages/python-sdk/simstudio/__init__.py new file mode 100644 index 00000000000..a857828b3e9 --- /dev/null +++ b/packages/python-sdk/simstudio/__init__.py @@ -0,0 +1,248 @@ +""" +Sim Studio SDK for Python + +Official Python SDK for Sim Studio, allowing you to execute workflows programmatically. +""" + +import json +import time +from typing import Any, Dict, Optional, Union +from dataclasses import dataclass + +import requests + + +__version__ = "0.1.0" +__all__ = ["SimStudioClient", "SimStudioError", "WorkflowExecutionResult", "WorkflowStatus"] + + +@dataclass +class WorkflowExecutionResult: + """Result of a workflow execution.""" + success: bool + output: Optional[Any] = None + error: Optional[str] = None + logs: Optional[list] = None + metadata: Optional[Dict[str, Any]] = None + trace_spans: Optional[list] = None + total_duration: Optional[float] = None + + +@dataclass +class WorkflowStatus: + """Status of a workflow.""" + is_deployed: bool + deployed_at: Optional[str] = None + is_published: bool = False + needs_redeployment: bool = False + + +class SimStudioError(Exception): + """Exception raised for Sim Studio API errors.""" + + def __init__(self, message: str, code: Optional[str] = None, status: Optional[int] = None): + super().__init__(message) + self.code = code + self.status = status + + +class SimStudioClient: + """ + Sim Studio API client for executing workflows programmatically. + + Args: + api_key: Your Sim Studio API key + base_url: Base URL for the Sim Studio API (defaults to https://simstudio.ai) + """ + + def __init__(self, api_key: str, base_url: str = "https://simstudio.ai"): + self.api_key = api_key + self.base_url = base_url.rstrip('/') + self._session = requests.Session() + self._session.headers.update({ + 'X-API-Key': self.api_key, + 'Content-Type': 'application/json', + }) + + def execute_workflow( + self, + workflow_id: str, + input_data: Optional[Dict[str, Any]] = None, + timeout: float = 30.0 + ) -> WorkflowExecutionResult: + """ + Execute a workflow with optional input data. + + Args: + workflow_id: The ID of the workflow to execute + input_data: Input data to pass to the workflow + timeout: Timeout in seconds (default: 30.0) + + Returns: + WorkflowExecutionResult object containing the execution result + + Raises: + SimStudioError: If the workflow execution fails + """ + url = f"{self.base_url}/api/workflows/{workflow_id}/execute" + + try: + if input_data: + response = self._session.post( + url, + json=input_data, + timeout=timeout + ) + else: + response = self._session.get(url, timeout=timeout) + + if not response.ok: + try: + error_data = response.json() + error_message = error_data.get('error', f'HTTP {response.status_code}: {response.reason}') + error_code = error_data.get('code') + except (ValueError, KeyError): + error_message = f'HTTP {response.status_code}: {response.reason}' + error_code = None + + raise SimStudioError(error_message, error_code, response.status_code) + + result_data = response.json() + + return WorkflowExecutionResult( + success=result_data.get('success', True), + output=result_data.get('output'), + error=result_data.get('error'), + logs=result_data.get('logs'), + metadata=result_data.get('metadata'), + trace_spans=result_data.get('traceSpans'), + total_duration=result_data.get('totalDuration') + ) + + except requests.Timeout: + raise SimStudioError(f'Workflow execution timed out after {timeout} seconds', 'TIMEOUT') + except requests.RequestException as e: + raise SimStudioError(f'Failed to execute workflow: {str(e)}', 'EXECUTION_ERROR') + + def get_workflow_status(self, workflow_id: str) -> WorkflowStatus: + """ + Get the status of a workflow (deployment status, etc.). + + Args: + workflow_id: The ID of the workflow + + Returns: + WorkflowStatus object containing the workflow status + + Raises: + SimStudioError: If getting the status fails + """ + url = f"{self.base_url}/api/workflows/{workflow_id}/status" + + try: + response = self._session.get(url) + + if not response.ok: + try: + error_data = response.json() + error_message = error_data.get('error', f'HTTP {response.status_code}: {response.reason}') + error_code = error_data.get('code') + except (ValueError, KeyError): + error_message = f'HTTP {response.status_code}: {response.reason}' + error_code = None + + raise SimStudioError(error_message, error_code, response.status_code) + + status_data = response.json() + + return WorkflowStatus( + is_deployed=status_data.get('isDeployed', False), + deployed_at=status_data.get('deployedAt'), + is_published=status_data.get('isPublished', False), + needs_redeployment=status_data.get('needsRedeployment', False) + ) + + except requests.RequestException as e: + raise SimStudioError(f'Failed to get workflow status: {str(e)}', 'STATUS_ERROR') + + def validate_workflow(self, workflow_id: str) -> bool: + """ + Validate that a workflow is ready for execution. + + Args: + workflow_id: The ID of the workflow + + Returns: + True if the workflow is deployed and ready, False otherwise + """ + try: + status = self.get_workflow_status(workflow_id) + return status.is_deployed + except SimStudioError: + return False + + def execute_workflow_sync( + self, + workflow_id: str, + input_data: Optional[Dict[str, Any]] = None, + timeout: float = 30.0, + poll_interval: float = 1.0, + max_wait_time: float = 300.0 + ) -> WorkflowExecutionResult: + """ + Execute a workflow and poll for completion (useful for long-running workflows). + + Note: Currently, the API is synchronous, so this method just calls execute_workflow. + In the future, if async execution is added, this method can be enhanced. + + Args: + workflow_id: The ID of the workflow to execute + input_data: Input data to pass to the workflow + timeout: Timeout for the initial request in seconds + poll_interval: Polling interval in seconds (default: 1.0) + max_wait_time: Maximum wait time in seconds (default: 300.0) + + Returns: + WorkflowExecutionResult object containing the execution result + + Raises: + SimStudioError: If the workflow execution fails + """ + # For now, the API is synchronous, so we just execute directly + # In the future, if async execution is added, this method can be enhanced + return self.execute_workflow(workflow_id, input_data, timeout) + + def set_api_key(self, api_key: str) -> None: + """ + Update the API key. + + Args: + api_key: New API key + """ + self.api_key = api_key + self._session.headers.update({'X-API-Key': api_key}) + + def set_base_url(self, base_url: str) -> None: + """ + Update the base URL. + + Args: + base_url: New base URL + """ + self.base_url = base_url.rstrip('/') + + def close(self) -> None: + """Close the underlying HTTP session.""" + self._session.close() + + def __enter__(self): + """Context manager entry.""" + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + """Context manager exit.""" + self.close() + + +# For backward compatibility +Client = SimStudioClient \ No newline at end of file diff --git a/packages/python-sdk/tests/__init__.py b/packages/python-sdk/tests/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/packages/python-sdk/tests/test_client.py b/packages/python-sdk/tests/test_client.py new file mode 100644 index 00000000000..7130b6d842a --- /dev/null +++ b/packages/python-sdk/tests/test_client.py @@ -0,0 +1,95 @@ +""" +Tests for the Sim Studio Python SDK +""" + +import pytest +from unittest.mock import Mock, patch +from simstudio import SimStudioClient, SimStudioError, WorkflowExecutionResult, WorkflowStatus + + +def test_simstudio_client_initialization(): + """Test SimStudioClient initialization.""" + client = SimStudioClient(api_key="test-api-key", base_url="https://test.simstudio.ai") + assert client.api_key == "test-api-key" + assert client.base_url == "https://test.simstudio.ai" + + +def test_simstudio_client_default_base_url(): + """Test SimStudioClient with default base URL.""" + client = SimStudioClient(api_key="test-api-key") + assert client.api_key == "test-api-key" + assert client.base_url == "https://simstudio.ai" + + +def test_set_api_key(): + """Test setting a new API key.""" + client = SimStudioClient(api_key="test-api-key") + client.set_api_key("new-api-key") + assert client.api_key == "new-api-key" + + +def test_set_base_url(): + """Test setting a new base URL.""" + client = SimStudioClient(api_key="test-api-key") + client.set_base_url("https://new.simstudio.ai/") + assert client.base_url == "https://new.simstudio.ai" + + +def test_set_base_url_strips_trailing_slash(): + """Test that base URL strips trailing slash.""" + client = SimStudioClient(api_key="test-api-key") + client.set_base_url("https://test.simstudio.ai/") + assert client.base_url == "https://test.simstudio.ai" + + +@patch('simstudio.requests.Session.get') +def test_validate_workflow_returns_false_on_error(mock_get): + """Test that validate_workflow returns False when request fails.""" + from simstudio import SimStudioError + mock_get.side_effect = SimStudioError("Network error") + + client = SimStudioClient(api_key="test-api-key") + result = client.validate_workflow("test-workflow-id") + + assert result is False + + +def test_simstudio_error(): + """Test SimStudioError creation.""" + error = SimStudioError("Test error", "TEST_CODE", 400) + assert str(error) == "Test error" + assert error.code == "TEST_CODE" + assert error.status == 400 + + +def test_workflow_execution_result(): + """Test WorkflowExecutionResult data class.""" + result = WorkflowExecutionResult( + success=True, + output={"data": "test"}, + metadata={"duration": 1000} + ) + assert result.success is True + assert result.output == {"data": "test"} + assert result.metadata == {"duration": 1000} + + +def test_workflow_status(): + """Test WorkflowStatus data class.""" + status = WorkflowStatus( + is_deployed=True, + deployed_at="2023-01-01T00:00:00Z", + is_published=False, + needs_redeployment=False + ) + assert status.is_deployed is True + assert status.deployed_at == "2023-01-01T00:00:00Z" + assert status.is_published is False + assert status.needs_redeployment is False + + +def test_context_manager(): + """Test SimStudioClient as context manager.""" + with SimStudioClient(api_key="test-api-key") as client: + assert client.api_key == "test-api-key" + # Should close without error \ No newline at end of file diff --git a/packages/ts-sdk/.gitignore b/packages/ts-sdk/.gitignore new file mode 100644 index 00000000000..dcb35fa34e8 --- /dev/null +++ b/packages/ts-sdk/.gitignore @@ -0,0 +1,43 @@ +# Dependencies +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Build output +dist/ +build/ + +# Coverage reports +coverage/ +*.lcov + +# Cache directories +.turbo/ +.cache/ +.parcel-cache/ + +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE +.vscode/ +.idea/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# TypeScript +*.tsbuildinfo + +# Logs +logs +*.log \ No newline at end of file diff --git a/packages/ts-sdk/README.md b/packages/ts-sdk/README.md new file mode 100644 index 00000000000..a05b7ae7689 --- /dev/null +++ b/packages/ts-sdk/README.md @@ -0,0 +1,275 @@ +# Sim Studio TypeScript SDK + +The official TypeScript/JavaScript SDK for [Sim Studio](https://simstudio.ai), allowing you to execute workflows programmatically from your applications. + +## Installation + +```bash +npm install simstudio-ts-sdk +# or +yarn add simstudio-ts-sdk +# or +bun add simstudio-ts-sdk +``` + +## Quick Start + +```typescript +import { SimStudioClient } from 'simstudio-ts-sdk'; + +// Initialize the client +const client = new SimStudioClient({ + apiKey: 'your-api-key-here', + baseUrl: 'https://simstudio.ai' // optional, defaults to https://simstudio.ai +}); + +// Execute a workflow +try { + const result = await client.executeWorkflow('workflow-id'); + console.log('Workflow executed successfully:', result); +} catch (error) { + console.error('Workflow execution failed:', error); +} +``` + +## API Reference + +### SimStudioClient + +#### Constructor + +```typescript +new SimStudioClient(config: SimStudioConfig) +``` + +- `config.apiKey` (string): Your Sim Studio API key +- `config.baseUrl` (string, optional): Base URL for the Sim Studio API (defaults to `https://simstudio.ai`) + +#### Methods + +##### executeWorkflow(workflowId, options?) + +Execute a workflow with optional input data. + +```typescript +const result = await client.executeWorkflow('workflow-id', { + input: { message: 'Hello, world!' }, + timeout: 30000 // 30 seconds +}); +``` + +**Parameters:** +- `workflowId` (string): The ID of the workflow to execute +- `options` (ExecutionOptions, optional): + - `input` (any): Input data to pass to the workflow + - `timeout` (number): Timeout in milliseconds (default: 30000) + +**Returns:** `Promise` + +##### getWorkflowStatus(workflowId) + +Get the status of a workflow (deployment status, etc.). + +```typescript +const status = await client.getWorkflowStatus('workflow-id'); +console.log('Is deployed:', status.isDeployed); +``` + +**Parameters:** +- `workflowId` (string): The ID of the workflow + +**Returns:** `Promise` + +##### validateWorkflow(workflowId) + +Validate that a workflow is ready for execution. + +```typescript +const isReady = await client.validateWorkflow('workflow-id'); +if (isReady) { + // Workflow is deployed and ready +} +``` + +**Parameters:** +- `workflowId` (string): The ID of the workflow + +**Returns:** `Promise` + +##### executeWorkflowSync(workflowId, options?) + +Execute a workflow and poll for completion (useful for long-running workflows). + +```typescript +const result = await client.executeWorkflowSync('workflow-id', { + input: { data: 'some input' }, + timeout: 60000, + pollInterval: 2000, + maxWaitTime: 300000 +}); +``` + +**Parameters:** +- `workflowId` (string): The ID of the workflow to execute +- `options` (ExecutionOptions & polling options, optional): + - `input` (any): Input data to pass to the workflow + - `timeout` (number): Timeout for the initial request in milliseconds + - `pollInterval` (number): Polling interval in milliseconds (default: 1000) + - `maxWaitTime` (number): Maximum wait time in milliseconds (default: 300000) + +**Returns:** `Promise` + +##### setApiKey(apiKey) + +Update the API key. + +```typescript +client.setApiKey('new-api-key'); +``` + +##### setBaseUrl(baseUrl) + +Update the base URL. + +```typescript +client.setBaseUrl('https://my-custom-domain.com'); +``` + +## Types + +### WorkflowExecutionResult + +```typescript +interface WorkflowExecutionResult { + success: boolean; + output?: any; + error?: string; + logs?: any[]; + metadata?: { + duration?: number; + executionId?: string; + [key: string]: any; + }; + traceSpans?: any[]; + totalDuration?: number; +} +``` + +### WorkflowStatus + +```typescript +interface WorkflowStatus { + isDeployed: boolean; + deployedAt?: string; + isPublished: boolean; + needsRedeployment: boolean; +} +``` + +### SimStudioError + +```typescript +class SimStudioError extends Error { + code?: string; + status?: number; +} +``` + +## Examples + +### Basic Workflow Execution + +```typescript +import { SimStudioClient } from 'simstudio-ts-sdk'; + +const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY! +}); + +async function runWorkflow() { + try { + // Check if workflow is ready + const isReady = await client.validateWorkflow('my-workflow-id'); + if (!isReady) { + throw new Error('Workflow is not deployed or ready'); + } + + // Execute the workflow + const result = await client.executeWorkflow('my-workflow-id', { + input: { + message: 'Process this data', + userId: '12345' + } + }); + + if (result.success) { + console.log('Output:', result.output); + console.log('Duration:', result.metadata?.duration); + } else { + console.error('Workflow failed:', result.error); + } + } catch (error) { + console.error('Error:', error); + } +} + +runWorkflow(); +``` + +### Error Handling + +```typescript +import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk'; + +const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY! +}); + +async function executeWithErrorHandling() { + try { + const result = await client.executeWorkflow('workflow-id'); + return result; + } catch (error) { + if (error instanceof SimStudioError) { + switch (error.code) { + case 'UNAUTHORIZED': + console.error('Invalid API key'); + break; + case 'TIMEOUT': + console.error('Workflow execution timed out'); + break; + case 'USAGE_LIMIT_EXCEEDED': + console.error('Usage limit exceeded'); + break; + default: + console.error('Workflow error:', error.message); + } + } else { + console.error('Unexpected error:', error); + } + throw error; + } +} +``` + +### Environment Configuration + +```typescript +// Using environment variables +const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY!, + baseUrl: process.env.SIMSTUDIO_BASE_URL // optional +}); +``` + +## Getting Your API Key + +1. Log in to your [Sim Studio](https://simstudio.ai) account +2. Navigate to your workflow +3. Click on "Deploy" to deploy your workflow +4. Select or create an API key during the deployment process +5. Copy the API key to use in your application + +## License + +Apache-2.0 \ No newline at end of file diff --git a/packages/ts-sdk/examples/basic-usage.ts b/packages/ts-sdk/examples/basic-usage.ts new file mode 100644 index 00000000000..c943aa755aa --- /dev/null +++ b/packages/ts-sdk/examples/basic-usage.ts @@ -0,0 +1,103 @@ +import { SimStudioClient, SimStudioError } from '../src/index' + +// Example 1: Basic workflow execution +async function basicExample() { + const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY!, + baseUrl: 'https://simstudio.ai', + }) + + try { + // Execute a workflow without input + const result = await client.executeWorkflow('your-workflow-id') + + if (result.success) { + console.log('āœ… Workflow executed successfully!') + console.log('Output:', result.output) + console.log('Duration:', result.metadata?.duration, 'ms') + } else { + console.log('āŒ Workflow failed:', result.error) + } + } catch (error) { + if (error instanceof SimStudioError) { + console.error('SDK Error:', error.message, 'Code:', error.code) + } else { + console.error('Unexpected error:', error) + } + } +} + +// Example 2: Workflow execution with input data +async function withInputExample() { + const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY!, + }) + + try { + const result = await client.executeWorkflow('your-workflow-id', { + input: { + message: 'Hello from SDK!', + userId: '12345', + data: { + type: 'analysis', + parameters: { + includeMetadata: true, + format: 'json', + }, + }, + }, + timeout: 60000, // 60 seconds + }) + + console.log('Execution result:', result) + } catch (error) { + console.error('Error:', error) + } +} + +// Example 3: Workflow validation and status checking +async function statusExample() { + const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY!, + }) + + try { + // Check if workflow is ready + const isReady = await client.validateWorkflow('your-workflow-id') + console.log('Workflow ready:', isReady) + + // Get detailed status + const status = await client.getWorkflowStatus('your-workflow-id') + console.log('Status:', { + deployed: status.isDeployed, + published: status.isPublished, + needsRedeployment: status.needsRedeployment, + deployedAt: status.deployedAt, + }) + + if (status.isDeployed) { + // Execute the workflow + const result = await client.executeWorkflow('your-workflow-id') + console.log('Result:', result) + } + } catch (error) { + console.error('Error:', error) + } +} + +// Run examples +if (require.main === module) { + console.log('šŸš€ Running Sim Studio SDK Examples\n') + + basicExample() + .then(() => console.log('\nāœ… Basic example completed')) + .catch(console.error) + + withInputExample() + .then(() => console.log('\nāœ… Input example completed')) + .catch(console.error) + + statusExample() + .then(() => console.log('\nāœ… Status example completed')) + .catch(console.error) +} diff --git a/packages/ts-sdk/package.json b/packages/ts-sdk/package.json new file mode 100644 index 00000000000..eccd1253316 --- /dev/null +++ b/packages/ts-sdk/package.json @@ -0,0 +1,53 @@ +{ + "name": "simstudio-ts-sdk", + "version": "0.1.0", + "description": "Sim Studio SDK - Execute workflows programmatically", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "bun run build:tsc", + "build:tsc": "tsc", + "dev": "tsc --watch", + "prepublishOnly": "bun run build", + "test": "vitest run", + "test:watch": "vitest" + }, + "files": [ + "dist" + ], + "keywords": [ + "simstudio", + "ai", + "workflow", + "sdk", + "api", + "automation", + "typescript" + ], + "author": "Sim Studio", + "license": "Apache-2.0", + "dependencies": { + "node-fetch": "^3.3.2" + }, + "devDependencies": { + "@types/node": "^20.5.1", + "typescript": "^5.1.6", + "vitest": "^3.0.8", + "@vitest/coverage-v8": "^3.0.8" + }, + "engines": { + "node": ">=16" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/simstudioai/sim.git", + "directory": "packages/ts-sdk" + }, + "homepage": "https://simstudio.ai", + "bugs": { + "url": "https://github.com/simstudioai/sim/issues" + } +} diff --git a/packages/ts-sdk/src/index.test.ts b/packages/ts-sdk/src/index.test.ts new file mode 100644 index 00000000000..987c2b661d9 --- /dev/null +++ b/packages/ts-sdk/src/index.test.ts @@ -0,0 +1,80 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { SimStudioClient, SimStudioError } from './index' + +vi.mock('node-fetch', () => ({ + default: vi.fn(), +})) + +describe('SimStudioClient', () => { + let client: SimStudioClient + + beforeEach(() => { + client = new SimStudioClient({ + apiKey: 'test-api-key', + baseUrl: 'https://test.simstudio.ai', + }) + vi.clearAllMocks() + }) + + describe('constructor', () => { + it('should create a client with correct configuration', () => { + expect(client).toBeInstanceOf(SimStudioClient) + }) + + it('should use default base URL when not provided', () => { + const defaultClient = new SimStudioClient({ + apiKey: 'test-api-key', + }) + expect(defaultClient).toBeInstanceOf(SimStudioClient) + }) + }) + + describe('setApiKey', () => { + it('should update the API key', () => { + const newApiKey = 'new-api-key' + client.setApiKey(newApiKey) + + // Verify the method exists + expect(client.setApiKey).toBeDefined() + }) + }) + + describe('setBaseUrl', () => { + it('should update the base URL', () => { + const newBaseUrl = 'https://new.simstudio.ai' + client.setBaseUrl(newBaseUrl) + expect(client.setBaseUrl).toBeDefined() + }) + + it('should strip trailing slash from base URL', () => { + const urlWithSlash = 'https://test.simstudio.ai/' + client.setBaseUrl(urlWithSlash) + expect(client.setBaseUrl).toBeDefined() + }) + }) + + describe('validateWorkflow', () => { + it('should return false when workflow status request fails', async () => { + const fetch = await import('node-fetch') + vi.mocked(fetch.default).mockRejectedValue(new Error('Network error')) + + const result = await client.validateWorkflow('test-workflow-id') + expect(result).toBe(false) + }) + }) +}) + +describe('SimStudioError', () => { + it('should create error with message', () => { + const error = new SimStudioError('Test error') + expect(error.message).toBe('Test error') + expect(error.name).toBe('SimStudioError') + }) + + it('should create error with code and status', () => { + const error = new SimStudioError('Test error', 'TEST_CODE', 400) + expect(error.message).toBe('Test error') + expect(error.code).toBe('TEST_CODE') + expect(error.status).toBe(400) + }) +}) diff --git a/packages/ts-sdk/src/index.ts b/packages/ts-sdk/src/index.ts new file mode 100644 index 00000000000..8225801ecac --- /dev/null +++ b/packages/ts-sdk/src/index.ts @@ -0,0 +1,182 @@ +import fetch from 'node-fetch' + +export interface SimStudioConfig { + apiKey: string + baseUrl?: string +} + +export interface WorkflowExecutionResult { + success: boolean + output?: any + error?: string + logs?: any[] + metadata?: { + duration?: number + executionId?: string + [key: string]: any + } + traceSpans?: any[] + totalDuration?: number +} + +export interface WorkflowStatus { + isDeployed: boolean + deployedAt?: string + isPublished: boolean + needsRedeployment: boolean +} + +export interface ExecutionOptions { + input?: any + timeout?: number +} + +export class SimStudioError extends Error { + public code?: string + public status?: number + + constructor(message: string, code?: string, status?: number) { + super(message) + this.name = 'SimStudioError' + this.code = code + this.status = status + } +} + +export class SimStudioClient { + private apiKey: string + private baseUrl: string + + constructor(config: SimStudioConfig) { + this.apiKey = config.apiKey + this.baseUrl = config.baseUrl || 'https://simstudio.ai' + } + + /** + * Execute a workflow with optional input data + */ + async executeWorkflow( + workflowId: string, + options: ExecutionOptions = {} + ): Promise { + const url = `${this.baseUrl}/api/workflows/${workflowId}/execute` + const { input, timeout = 30000 } = options + + try { + // Create a timeout promise + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => reject(new Error('TIMEOUT')), timeout) + }) + + const fetchPromise = fetch(url, { + method: input ? 'POST' : 'GET', + headers: { + 'Content-Type': 'application/json', + 'X-API-Key': this.apiKey, + }, + body: input ? JSON.stringify(input) : undefined, + }) + + const response = await Promise.race([fetchPromise, timeoutPromise]) + + if (!response.ok) { + const errorData = (await response.json().catch(() => ({}))) as any + throw new SimStudioError( + errorData.error || `HTTP ${response.status}: ${response.statusText}`, + errorData.code, + response.status + ) + } + + const result = await response.json() + return result as WorkflowExecutionResult + } catch (error: any) { + if (error instanceof SimStudioError) { + throw error + } + + if (error.message === 'TIMEOUT') { + throw new SimStudioError(`Workflow execution timed out after ${timeout}ms`, 'TIMEOUT') + } + + throw new SimStudioError(error?.message || 'Failed to execute workflow', 'EXECUTION_ERROR') + } + } + + /** + * Get the status of a workflow (deployment status, etc.) + */ + async getWorkflowStatus(workflowId: string): Promise { + const url = `${this.baseUrl}/api/workflows/${workflowId}/status` + + try { + const response = await fetch(url, { + method: 'GET', + headers: { + 'X-API-Key': this.apiKey, + }, + }) + + if (!response.ok) { + const errorData = (await response.json().catch(() => ({}))) as any + throw new SimStudioError( + errorData.error || `HTTP ${response.status}: ${response.statusText}`, + errorData.code, + response.status + ) + } + + const result = await response.json() + return result as WorkflowStatus + } catch (error: any) { + if (error instanceof SimStudioError) { + throw error + } + + throw new SimStudioError(error?.message || 'Failed to get workflow status', 'STATUS_ERROR') + } + } + + /** + * Execute a workflow and poll for completion (useful for long-running workflows) + */ + async executeWorkflowSync( + workflowId: string, + options: ExecutionOptions & { pollInterval?: number; maxWaitTime?: number } = {} + ): Promise { + const { pollInterval = 1000, maxWaitTime = 300000, ...executeOptions } = options + + // For now, the API is synchronous, so we just execute directly + // In the future, if async execution is added, this method can be enhanced + return this.executeWorkflow(workflowId, executeOptions) + } + + /** + * Validate that a workflow is ready for execution + */ + async validateWorkflow(workflowId: string): Promise { + try { + const status = await this.getWorkflowStatus(workflowId) + return status.isDeployed + } catch (error) { + return false + } + } + + /** + * Set a new API key + */ + setApiKey(apiKey: string): void { + this.apiKey = apiKey + } + + /** + * Set a new base URL + */ + setBaseUrl(baseUrl: string): void { + this.baseUrl = baseUrl + } +} + +// Export types and classes +export { SimStudioClient as default } diff --git a/packages/ts-sdk/tsconfig.json b/packages/ts-sdk/tsconfig.json new file mode 100644 index 00000000000..465038368d9 --- /dev/null +++ b/packages/ts-sdk/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "moduleResolution": "node", + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "**/*.test.ts"] +} diff --git a/packages/ts-sdk/vitest.config.ts b/packages/ts-sdk/vitest.config.ts new file mode 100644 index 00000000000..3e442d37705 --- /dev/null +++ b/packages/ts-sdk/vitest.config.ts @@ -0,0 +1,11 @@ +/// +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/**/*.test.{ts,tsx}', 'tests/**/*.test.{ts,tsx}'], + exclude: ['**/node_modules/**', '**/dist/**'], + }, +}) diff --git a/turbo.json b/turbo.json index 2261a41aff6..4f852d7739e 100644 --- a/turbo.json +++ b/turbo.json @@ -5,7 +5,13 @@ "build": { "dependsOn": ["^build"], "inputs": ["$TURBO_DEFAULT$", ".env*"], - "outputs": [".next/**", "!.next/cache/**", "packages/simstudio/dist/**"] + "outputs": [ + ".next/**", + "!.next/cache/**", + "packages/cli/dist/**", + "packages/ts-sdk/dist/**", + "packages/python-sdk/dist/**" + ] }, "dev": { "persistent": true, From 6b15b6802928235b9253695951724e13c769b643 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 17 Jun 2025 20:50:24 -0700 Subject: [PATCH 2/4] added docs --- apps/docs/content/docs/meta.json | 5 +- apps/docs/content/docs/sdks/python.mdx | 408 ++++++++++++++ apps/docs/content/docs/sdks/typescript.mdx | 592 +++++++++++++++++++++ turbo.json | 8 +- 4 files changed, 1005 insertions(+), 8 deletions(-) create mode 100644 apps/docs/content/docs/sdks/python.mdx create mode 100644 apps/docs/content/docs/sdks/typescript.mdx diff --git a/apps/docs/content/docs/meta.json b/apps/docs/content/docs/meta.json index 953c887daab..6b37a43525f 100644 --- a/apps/docs/content/docs/meta.json +++ b/apps/docs/content/docs/meta.json @@ -12,7 +12,10 @@ "---Execution---", "execution", "---Advanced---", - "./variables/index" + "./variables/index", + "---SDKs---", + "./sdks/python", + "./sdks/typescript" ], "defaultOpen": true } diff --git a/apps/docs/content/docs/sdks/python.mdx b/apps/docs/content/docs/sdks/python.mdx new file mode 100644 index 00000000000..86d0915b8da --- /dev/null +++ b/apps/docs/content/docs/sdks/python.mdx @@ -0,0 +1,408 @@ +--- +title: Python SDK +description: The official Python SDK for Sim Studio +--- + +import { Callout } from 'fumadocs-ui/components/callout' +import { Card, Cards } from 'fumadocs-ui/components/card' +import { Step, Steps } from 'fumadocs-ui/components/steps' +import { Tab, Tabs } from 'fumadocs-ui/components/tabs' + +The official Python SDK for Sim Studio allows you to execute workflows programmatically from your Python applications. + + + The Python SDK supports Python 3.8+ and provides synchronous workflow execution. All workflow executions are currently synchronous. + + +## Installation + +Install the SDK using pip: + +```bash +pip install simstudio-sdk +``` + +## Quick Start + +Here's a simple example to get you started: + +```python +from simstudio import SimStudioClient + +# Initialize the client +client = SimStudioClient( + api_key="your-api-key-here", + base_url="https://simstudio.ai" # optional, defaults to https://simstudio.ai +) + +# Execute a workflow +try: + result = client.execute_workflow("workflow-id") + print("Workflow executed successfully:", result) +except Exception as error: + print("Workflow execution failed:", error) +``` + +## API Reference + +### SimStudioClient + +#### Constructor + +```python +SimStudioClient(api_key: str, base_url: str = "https://simstudio.ai") +``` + +**Parameters:** +- `api_key` (str): Your Sim Studio API key +- `base_url` (str, optional): Base URL for the Sim Studio API + +#### Methods + +##### execute_workflow() + +Execute a workflow with optional input data. + +```python +result = client.execute_workflow( + "workflow-id", + input_data={"message": "Hello, world!"}, + timeout=30.0 # 30 seconds +) +``` + +**Parameters:** +- `workflow_id` (str): The ID of the workflow to execute +- `input_data` (dict, optional): Input data to pass to the workflow +- `timeout` (float): Timeout in seconds (default: 30.0) + +**Returns:** `WorkflowExecutionResult` + +##### get_workflow_status() + +Get the status of a workflow (deployment status, etc.). + +```python +status = client.get_workflow_status("workflow-id") +print("Is deployed:", status.is_deployed) +``` + +**Parameters:** +- `workflow_id` (str): The ID of the workflow + +**Returns:** `WorkflowStatus` + +##### validate_workflow() + +Validate that a workflow is ready for execution. + +```python +is_ready = client.validate_workflow("workflow-id") +if is_ready: + # Workflow is deployed and ready + pass +``` + +**Parameters:** +- `workflow_id` (str): The ID of the workflow + +**Returns:** `bool` + +##### execute_workflow_sync() + + + Currently, this method is identical to `execute_workflow()` since all executions are synchronous. This method is provided for future compatibility when asynchronous execution is added. + + +Execute a workflow (currently synchronous, same as `execute_workflow()`). + +```python +result = client.execute_workflow_sync( + "workflow-id", + input_data={"data": "some input"}, + timeout=60.0 +) +``` + +**Parameters:** +- `workflow_id` (str): The ID of the workflow to execute +- `input_data` (dict, optional): Input data to pass to the workflow +- `timeout` (float): Timeout for the initial request in seconds +- `poll_interval` (float): Reserved for future async support (currently unused) +- `max_wait_time` (float): Reserved for future async support (currently unused) + +**Returns:** `WorkflowExecutionResult` + +##### set_api_key() + +Update the API key. + +```python +client.set_api_key("new-api-key") +``` + +##### set_base_url() + +Update the base URL. + +```python +client.set_base_url("https://my-custom-domain.com") +``` + +##### close() + +Close the underlying HTTP session. + +```python +client.close() +``` + +## Data Classes + +### WorkflowExecutionResult + +```python +@dataclass +class WorkflowExecutionResult: + success: bool + output: Optional[Any] = None + error: Optional[str] = None + logs: Optional[list] = None + metadata: Optional[Dict[str, Any]] = None + trace_spans: Optional[list] = None + total_duration: Optional[float] = None +``` + +### WorkflowStatus + +```python +@dataclass +class WorkflowStatus: + is_deployed: bool + deployed_at: Optional[str] = None + is_published: bool = False + needs_redeployment: bool = False +``` + +### SimStudioError + +```python +class SimStudioError(Exception): + def __init__(self, message: str, code: Optional[str] = None, status: Optional[int] = None): + self.code = code + self.status = status +``` + +## Examples + +### Basic Workflow Execution + + + + Set up the SimStudioClient with your API key. + + + Check if the workflow is deployed and ready for execution. + + + Run the workflow with your input data. + + + Process the execution result and handle any errors. + + + +```python +import os +from simstudio import SimStudioClient + +client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) + +def run_workflow(): + try: + # Check if workflow is ready + is_ready = client.validate_workflow("my-workflow-id") + if not is_ready: + raise Exception("Workflow is not deployed or ready") + + # Execute the workflow + result = client.execute_workflow( + "my-workflow-id", + input_data={ + "message": "Process this data", + "user_id": "12345" + } + ) + + if result.success: + print("Output:", result.output) + print("Duration:", result.metadata.get("duration") if result.metadata else None) + else: + print("Workflow failed:", result.error) + + except Exception as error: + print("Error:", error) + +run_workflow() +``` + +### Error Handling + +Handle different types of errors that may occur during workflow execution: + +```python +from simstudio import SimStudioClient, SimStudioError +import os + +client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) + +def execute_with_error_handling(): + try: + result = client.execute_workflow("workflow-id") + return result + except SimStudioError as error: + if error.code == "UNAUTHORIZED": + print("Invalid API key") + elif error.code == "TIMEOUT": + print("Workflow execution timed out") + elif error.code == "USAGE_LIMIT_EXCEEDED": + print("Usage limit exceeded") + else: + print(f"Workflow error: {error}") + raise + except Exception as error: + print(f"Unexpected error: {error}") + raise +``` + +### Context Manager Usage + +Use the client as a context manager to automatically handle resource cleanup: + +```python +from simstudio import SimStudioClient +import os + +# Using context manager to automatically close the session +with SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) as client: + result = client.execute_workflow("workflow-id") + print("Result:", result) +# Session is automatically closed here +``` + +### Batch Workflow Execution + +Execute multiple workflows efficiently: + +```python +from simstudio import SimStudioClient +import os + +client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) + +def execute_workflows_batch(workflow_data_pairs): + """Execute multiple workflows with different input data.""" + results = [] + + for workflow_id, input_data in workflow_data_pairs: + try: + # Validate workflow before execution + if not client.validate_workflow(workflow_id): + print(f"Skipping {workflow_id}: not deployed") + continue + + result = client.execute_workflow(workflow_id, input_data) + results.append({ + "workflow_id": workflow_id, + "success": result.success, + "output": result.output, + "error": result.error + }) + + except Exception as error: + results.append({ + "workflow_id": workflow_id, + "success": False, + "error": str(error) + }) + + return results + +# Example usage +workflows = [ + ("workflow-1", {"type": "analysis", "data": "sample1"}), + ("workflow-2", {"type": "processing", "data": "sample2"}), +] + +results = execute_workflows_batch(workflows) +for result in results: + print(f"Workflow {result['workflow_id']}: {'Success' if result['success'] else 'Failed'}") +``` + +### Environment Configuration + +Configure the client using environment variables: + + + + ```python + import os + from simstudio import SimStudioClient + + # Development configuration + client = SimStudioClient( + api_key=os.getenv("SIMSTUDIO_API_KEY"), + base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://simstudio.ai") + ) + ``` + + + ```python + import os + from simstudio import SimStudioClient + + # Production configuration with error handling + api_key = os.getenv("SIMSTUDIO_API_KEY") + if not api_key: + raise ValueError("SIMSTUDIO_API_KEY environment variable is required") + + client = SimStudioClient( + api_key=api_key, + base_url=os.getenv("SIMSTUDIO_BASE_URL", "https://simstudio.ai") + ) + ``` + + + +## Getting Your API Key + + + + Navigate to [Sim Studio](https://simstudio.ai) and log in to your account. + + + Navigate to the workflow you want to execute programmatically. + + + Click on "Deploy" to deploy your workflow if it hasn't been deployed yet. + + + During the deployment process, select or create an API key. + + + Copy the API key to use in your Python application. + + + + + Keep your API key secure and never commit it to version control. Use environment variables or secure configuration management. + + +## Requirements + +- Python 3.8+ +- requests >= 2.25.0 + +## License + +Apache-2.0 \ No newline at end of file diff --git a/apps/docs/content/docs/sdks/typescript.mdx b/apps/docs/content/docs/sdks/typescript.mdx new file mode 100644 index 00000000000..7edcc173729 --- /dev/null +++ b/apps/docs/content/docs/sdks/typescript.mdx @@ -0,0 +1,592 @@ +--- +title: TypeScript/JavaScript SDK +description: The official TypeScript/JavaScript SDK for Sim Studio +--- + +import { Callout } from 'fumadocs-ui/components/callout' +import { Card, Cards } from 'fumadocs-ui/components/card' +import { Step, Steps } from 'fumadocs-ui/components/steps' +import { Tab, Tabs } from 'fumadocs-ui/components/tabs' + +The official TypeScript/JavaScript SDK for Sim Studio allows you to execute workflows programmatically from your Node.js applications, web applications, and other JavaScript environments. + + + The TypeScript SDK provides full type safety and supports both Node.js and browser environments. All workflow executions are currently synchronous. + + +## Installation + +Install the SDK using your preferred package manager: + + + + ```bash + npm install simstudio-ts-sdk + ``` + + + ```bash + yarn add simstudio-ts-sdk + ``` + + + ```bash + bun add simstudio-ts-sdk + ``` + + + +## Quick Start + +Here's a simple example to get you started: + +```typescript +import { SimStudioClient } from 'simstudio-ts-sdk'; + +// Initialize the client +const client = new SimStudioClient({ + apiKey: 'your-api-key-here', + baseUrl: 'https://simstudio.ai' // optional, defaults to https://simstudio.ai +}); + +// Execute a workflow +try { + const result = await client.executeWorkflow('workflow-id'); + console.log('Workflow executed successfully:', result); +} catch (error) { + console.error('Workflow execution failed:', error); +} +``` + +## API Reference + +### SimStudioClient + +#### Constructor + +```typescript +new SimStudioClient(config: SimStudioConfig) +``` + +**Configuration:** +- `config.apiKey` (string): Your Sim Studio API key +- `config.baseUrl` (string, optional): Base URL for the Sim Studio API (defaults to `https://simstudio.ai`) + +#### Methods + +##### executeWorkflow() + +Execute a workflow with optional input data. + +```typescript +const result = await client.executeWorkflow('workflow-id', { + input: { message: 'Hello, world!' }, + timeout: 30000 // 30 seconds +}); +``` + +**Parameters:** +- `workflowId` (string): The ID of the workflow to execute +- `options` (ExecutionOptions, optional): + - `input` (any): Input data to pass to the workflow + - `timeout` (number): Timeout in milliseconds (default: 30000) + +**Returns:** `Promise` + +##### getWorkflowStatus() + +Get the status of a workflow (deployment status, etc.). + +```typescript +const status = await client.getWorkflowStatus('workflow-id'); +console.log('Is deployed:', status.isDeployed); +``` + +**Parameters:** +- `workflowId` (string): The ID of the workflow + +**Returns:** `Promise` + +##### validateWorkflow() + +Validate that a workflow is ready for execution. + +```typescript +const isReady = await client.validateWorkflow('workflow-id'); +if (isReady) { + // Workflow is deployed and ready +} +``` + +**Parameters:** +- `workflowId` (string): The ID of the workflow + +**Returns:** `Promise` + +##### executeWorkflowSync() + + + Currently, this method is identical to `executeWorkflow()` since all executions are synchronous. This method is provided for future compatibility when asynchronous execution is added. + + +Execute a workflow (currently synchronous, same as `executeWorkflow()`). + +```typescript +const result = await client.executeWorkflowSync('workflow-id', { + input: { data: 'some input' }, + timeout: 60000 +}); +``` + +**Parameters:** +- `workflowId` (string): The ID of the workflow to execute +- `options` (ExecutionOptions & polling options, optional): + - `input` (any): Input data to pass to the workflow + - `timeout` (number): Timeout for the initial request in milliseconds + - `pollInterval` (number): Reserved for future async support (currently unused) + - `maxWaitTime` (number): Reserved for future async support (currently unused) + +**Returns:** `Promise` + +##### setApiKey() + +Update the API key. + +```typescript +client.setApiKey('new-api-key'); +``` + +##### setBaseUrl() + +Update the base URL. + +```typescript +client.setBaseUrl('https://my-custom-domain.com'); +``` + +## Types + +### WorkflowExecutionResult + +```typescript +interface WorkflowExecutionResult { + success: boolean; + output?: any; + error?: string; + logs?: any[]; + metadata?: { + duration?: number; + executionId?: string; + [key: string]: any; + }; + traceSpans?: any[]; + totalDuration?: number; +} +``` + +### WorkflowStatus + +```typescript +interface WorkflowStatus { + isDeployed: boolean; + deployedAt?: string; + isPublished: boolean; + needsRedeployment: boolean; +} +``` + +### SimStudioError + +```typescript +class SimStudioError extends Error { + code?: string; + status?: number; +} +``` + +## Examples + +### Basic Workflow Execution + + + + Set up the SimStudioClient with your API key. + + + Check if the workflow is deployed and ready for execution. + + + Run the workflow with your input data. + + + Process the execution result and handle any errors. + + + +```typescript +import { SimStudioClient } from 'simstudio-ts-sdk'; + +const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY! +}); + +async function runWorkflow() { + try { + // Check if workflow is ready + const isReady = await client.validateWorkflow('my-workflow-id'); + if (!isReady) { + throw new Error('Workflow is not deployed or ready'); + } + + // Execute the workflow + const result = await client.executeWorkflow('my-workflow-id', { + input: { + message: 'Process this data', + userId: '12345' + } + }); + + if (result.success) { + console.log('Output:', result.output); + console.log('Duration:', result.metadata?.duration); + } else { + console.error('Workflow failed:', result.error); + } + } catch (error) { + console.error('Error:', error); + } +} + +runWorkflow(); +``` + +### Error Handling + +Handle different types of errors that may occur during workflow execution: + +```typescript +import { SimStudioClient, SimStudioError } from 'simstudio-ts-sdk'; + +const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY! +}); + +async function executeWithErrorHandling() { + try { + const result = await client.executeWorkflow('workflow-id'); + return result; + } catch (error) { + if (error instanceof SimStudioError) { + switch (error.code) { + case 'UNAUTHORIZED': + console.error('Invalid API key'); + break; + case 'TIMEOUT': + console.error('Workflow execution timed out'); + break; + case 'USAGE_LIMIT_EXCEEDED': + console.error('Usage limit exceeded'); + break; + default: + console.error('Workflow error:', error.message); + } + } else { + console.error('Unexpected error:', error); + } + throw error; + } +} +``` + +### Environment Configuration + +Configure the client using environment variables: + + + + ```typescript + import { SimStudioClient } from 'simstudio-ts-sdk'; + + // Development configuration + const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY!, + baseUrl: process.env.SIMSTUDIO_BASE_URL // optional + }); + ``` + + + ```typescript + import { SimStudioClient } from 'simstudio-ts-sdk'; + + // Production configuration with validation + const apiKey = process.env.SIMSTUDIO_API_KEY; + if (!apiKey) { + throw new Error('SIMSTUDIO_API_KEY environment variable is required'); + } + + const client = new SimStudioClient({ + apiKey, + baseUrl: process.env.SIMSTUDIO_BASE_URL || 'https://simstudio.ai' + }); + ``` + + + +### Node.js Express Integration + +Integrate with an Express.js server: + +```typescript +import express from 'express'; +import { SimStudioClient } from 'simstudio-ts-sdk'; + +const app = express(); +const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY! +}); + +app.use(express.json()); + +app.post('/execute-workflow', async (req, res) => { + try { + const { workflowId, input } = req.body; + + const result = await client.executeWorkflow(workflowId, { + input, + timeout: 60000 + }); + + res.json({ + success: true, + data: result + }); + } catch (error) { + console.error('Workflow execution error:', error); + res.status(500).json({ + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }); + } +}); + +app.listen(3000, () => { + console.log('Server running on port 3000'); +}); +``` + +### Next.js API Route + +Use with Next.js API routes: + +```typescript +// pages/api/workflow.ts or app/api/workflow/route.ts +import { NextApiRequest, NextApiResponse } from 'next'; +import { SimStudioClient } from 'simstudio-ts-sdk'; + +const client = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY! +}); + +export default async function handler( + req: NextApiRequest, + res: NextApiResponse +) { + if (req.method !== 'POST') { + return res.status(405).json({ error: 'Method not allowed' }); + } + + try { + const { workflowId, input } = req.body; + + const result = await client.executeWorkflow(workflowId, { + input, + timeout: 30000 + }); + + res.status(200).json(result); + } catch (error) { + console.error('Error executing workflow:', error); + res.status(500).json({ + error: 'Failed to execute workflow' + }); + } +} +``` + +### Browser Usage + +Use in the browser (with proper CORS configuration): + +```typescript +import { SimStudioClient } from 'simstudio-ts-sdk'; + +// Note: In production, use a proxy server to avoid exposing API keys +const client = new SimStudioClient({ + apiKey: 'your-public-api-key', // Use with caution in browser + baseUrl: 'https://simstudio.ai' +}); + +async function executeClientSideWorkflow() { + try { + const result = await client.executeWorkflow('workflow-id', { + input: { + userInput: 'Hello from browser' + } + }); + + console.log('Workflow result:', result); + + // Update UI with result + document.getElementById('result')!.textContent = + JSON.stringify(result.output, null, 2); + } catch (error) { + console.error('Error:', error); + } +} + +// Attach to button click +document.getElementById('executeBtn')?.addEventListener('click', executeClientSideWorkflow); +``` + + + When using the SDK in the browser, be careful not to expose sensitive API keys. Consider using a backend proxy or public API keys with limited permissions. + + +### React Hook Example + +Create a custom React hook for workflow execution: + +```typescript +import { useState, useCallback } from 'react'; +import { SimStudioClient, WorkflowExecutionResult } from 'simstudio-ts-sdk'; + +const client = new SimStudioClient({ + apiKey: process.env.NEXT_PUBLIC_SIMSTUDIO_API_KEY! +}); + +interface UseWorkflowResult { + result: WorkflowExecutionResult | null; + loading: boolean; + error: Error | null; + executeWorkflow: (workflowId: string, input?: any) => Promise; +} + +export function useWorkflow(): UseWorkflowResult { + const [result, setResult] = useState(null); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + const executeWorkflow = useCallback(async (workflowId: string, input?: any) => { + setLoading(true); + setError(null); + setResult(null); + + try { + const workflowResult = await client.executeWorkflow(workflowId, { + input, + timeout: 30000 + }); + setResult(workflowResult); + } catch (err) { + setError(err instanceof Error ? err : new Error('Unknown error')); + } finally { + setLoading(false); + } + }, []); + + return { + result, + loading, + error, + executeWorkflow + }; +} + +// Usage in component +function WorkflowComponent() { + const { result, loading, error, executeWorkflow } = useWorkflow(); + + const handleExecute = () => { + executeWorkflow('my-workflow-id', { + message: 'Hello from React!' + }); + }; + + return ( +
+ + + {error &&
Error: {error.message}
} + {result && ( +
+

Result:

+
{JSON.stringify(result, null, 2)}
+
+ )} +
+ ); +} +``` + +## Getting Your API Key + + + + Navigate to [Sim Studio](https://simstudio.ai) and log in to your account. + + + Navigate to the workflow you want to execute programmatically. + + + Click on "Deploy" to deploy your workflow if it hasn't been deployed yet. + + + During the deployment process, select or create an API key. + + + Copy the API key to use in your TypeScript/JavaScript application. + + + + + Keep your API key secure and never commit it to version control. Use environment variables or secure configuration management. + + +## Requirements + +- Node.js 16+ +- TypeScript 5.0+ (for TypeScript projects) + +## TypeScript Support + +The SDK is written in TypeScript and provides full type safety: + +```typescript +import { + SimStudioClient, + WorkflowExecutionResult, + WorkflowStatus, + SimStudioError +} from 'simstudio-ts-sdk'; + +// Type-safe client initialization +const client: SimStudioClient = new SimStudioClient({ + apiKey: process.env.SIMSTUDIO_API_KEY! +}); + +// Type-safe workflow execution +const result: WorkflowExecutionResult = await client.executeWorkflow('workflow-id', { + input: { + message: 'Hello, TypeScript!' + } +}); + +// Type-safe status checking +const status: WorkflowStatus = await client.getWorkflowStatus('workflow-id'); +``` + +## License + +Apache-2.0 \ No newline at end of file diff --git a/turbo.json b/turbo.json index 4f852d7739e..29ab9cea453 100644 --- a/turbo.json +++ b/turbo.json @@ -5,13 +5,7 @@ "build": { "dependsOn": ["^build"], "inputs": ["$TURBO_DEFAULT$", ".env*"], - "outputs": [ - ".next/**", - "!.next/cache/**", - "packages/cli/dist/**", - "packages/ts-sdk/dist/**", - "packages/python-sdk/dist/**" - ] + "outputs": [".next/**", "!.next/cache/**", "dist/**"] }, "dev": { "persistent": true, From 3461314a5c7cb09391fd7f026280849b1a8ce399 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 20 Jun 2025 10:36:45 -0700 Subject: [PATCH 3/4] ack PR comments --- .github/workflows/publish-python-sdk.yml | 9 ++-- .github/workflows/publish-ts-sdk.yml | 6 +-- apps/docs/content/docs/sdks/python.mdx | 5 +- apps/docs/content/docs/sdks/typescript.mdx | 7 +-- packages/README.md | 4 +- packages/python-sdk/.gitignore | 1 - packages/python-sdk/README.md | 11 ++--- packages/python-sdk/examples/basic_usage.py | 28 ++++++++--- packages/python-sdk/simstudio/__init__.py | 23 +++------ packages/python-sdk/tests/test_client.py | 1 - packages/ts-sdk/README.md | 11 ++--- packages/ts-sdk/examples/basic-usage.ts | 55 +++++++++++++++------ packages/ts-sdk/src/index.test.ts | 22 ++++++++- packages/ts-sdk/src/index.ts | 14 +++--- 14 files changed, 124 insertions(+), 73 deletions(-) diff --git a/.github/workflows/publish-python-sdk.yml b/.github/workflows/publish-python-sdk.yml index bd5bdebfdd5..6892405de1f 100644 --- a/.github/workflows/publish-python-sdk.yml +++ b/.github/workflows/publish-python-sdk.yml @@ -17,11 +17,12 @@ jobs: uses: actions/setup-python@v5 with: python-version: '3.12' + cache: 'pip' - name: Install build dependencies run: | python -m pip install --upgrade pip - pip install build twine pytest requests + pip install build twine pytest requests tomli - name: Run tests working-directory: packages/python-sdk @@ -31,7 +32,7 @@ jobs: - name: Get package version id: package_version working-directory: packages/python-sdk - run: echo "version=$(python -c "import re; print(re.search(r'version = \"(.+?)\"', open('pyproject.toml').read()).group(1))")" >> $GITHUB_OUTPUT + run: echo "version=$(python -c "import tomli; print(tomli.load(open('pyproject.toml', 'rb'))['project']['version'])")" >> $GITHUB_OUTPUT - name: Check if version already exists id: version_check @@ -66,12 +67,12 @@ jobs: - name: Create GitHub Release if: steps.version_check.outputs.exists == 'false' - uses: actions/create-release@v1 + uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: python-sdk-v${{ steps.package_version.outputs.version }} - release_name: Python SDK v${{ steps.package_version.outputs.version }} + name: Python SDK v${{ steps.package_version.outputs.version }} body: | ## Python SDK v${{ steps.package_version.outputs.version }} diff --git a/.github/workflows/publish-ts-sdk.yml b/.github/workflows/publish-ts-sdk.yml index 2d0b4316297..360f5aa20a6 100644 --- a/.github/workflows/publish-ts-sdk.yml +++ b/.github/workflows/publish-ts-sdk.yml @@ -21,7 +21,7 @@ jobs: - name: Setup Node.js for npm publishing uses: actions/setup-node@v4 with: - node-version: '20' + node-version: '18' registry-url: 'https://registry.npmjs.org/' - name: Install dependencies @@ -63,12 +63,12 @@ jobs: - name: Create GitHub Release if: steps.version_check.outputs.exists == 'false' - uses: actions/create-release@v1 + uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: tag_name: typescript-sdk-v${{ steps.package_version.outputs.version }} - release_name: TypeScript SDK v${{ steps.package_version.outputs.version }} + name: TypeScript SDK v${{ steps.package_version.outputs.version }} body: | ## TypeScript SDK v${{ steps.package_version.outputs.version }} diff --git a/apps/docs/content/docs/sdks/python.mdx b/apps/docs/content/docs/sdks/python.mdx index 86d0915b8da..ecf4b2d7c59 100644 --- a/apps/docs/content/docs/sdks/python.mdx +++ b/apps/docs/content/docs/sdks/python.mdx @@ -128,8 +128,6 @@ result = client.execute_workflow_sync( - `workflow_id` (str): The ID of the workflow to execute - `input_data` (dict, optional): Input data to pass to the workflow - `timeout` (float): Timeout for the initial request in seconds -- `poll_interval` (float): Reserved for future async support (currently unused) -- `max_wait_time` (float): Reserved for future async support (currently unused) **Returns:** `WorkflowExecutionResult` @@ -189,6 +187,7 @@ class WorkflowStatus: ```python class SimStudioError(Exception): def __init__(self, message: str, code: Optional[str] = None, status: Optional[int] = None): + super().__init__(message) self.code = code self.status = status ``` @@ -267,6 +266,8 @@ def execute_with_error_handling(): print("Workflow execution timed out") elif error.code == "USAGE_LIMIT_EXCEEDED": print("Usage limit exceeded") + elif error.code == "INVALID_JSON": + print("Invalid JSON in request body") else: print(f"Workflow error: {error}") raise diff --git a/apps/docs/content/docs/sdks/typescript.mdx b/apps/docs/content/docs/sdks/typescript.mdx index 7edcc173729..a831c047ce2 100644 --- a/apps/docs/content/docs/sdks/typescript.mdx +++ b/apps/docs/content/docs/sdks/typescript.mdx @@ -140,11 +140,9 @@ const result = await client.executeWorkflowSync('workflow-id', { **Parameters:** - `workflowId` (string): The ID of the workflow to execute -- `options` (ExecutionOptions & polling options, optional): +- `options` (ExecutionOptions, optional): - `input` (any): Input data to pass to the workflow - `timeout` (number): Timeout for the initial request in milliseconds - - `pollInterval` (number): Reserved for future async support (currently unused) - - `maxWaitTime` (number): Reserved for future async support (currently unused) **Returns:** `Promise` @@ -287,6 +285,9 @@ async function executeWithErrorHandling() { case 'USAGE_LIMIT_EXCEEDED': console.error('Usage limit exceeded'); break; + case 'INVALID_JSON': + console.error('Invalid JSON in request body'); + break; default: console.error('Workflow error:', error.message); } diff --git a/packages/README.md b/packages/README.md index 3076db2434b..625c836460b 100644 --- a/packages/README.md +++ b/packages/README.md @@ -73,8 +73,7 @@ Both SDKs provide the same core functionality: Both SDKs are built on top of the same REST API endpoints: -- `POST /api/workflows/{id}/execute` - Execute workflow with input -- `GET /api/workflows/{id}/execute` - Execute workflow without input +- `POST /api/workflows/{id}/execute` - Execute workflow (with or without input) - `GET /api/workflows/{id}/status` - Get workflow status ## Authentication @@ -107,6 +106,7 @@ Both SDKs provide consistent error handling with these error codes: | `UNAUTHORIZED` | Invalid API key | | `TIMEOUT` | Request timed out | | `USAGE_LIMIT_EXCEEDED` | Account usage limit exceeded | +| `INVALID_JSON` | Invalid JSON in request body | | `EXECUTION_ERROR` | General execution error | | `STATUS_ERROR` | Error getting workflow status | diff --git a/packages/python-sdk/.gitignore b/packages/python-sdk/.gitignore index db85d8fa26b..db9927b2591 100644 --- a/packages/python-sdk/.gitignore +++ b/packages/python-sdk/.gitignore @@ -50,7 +50,6 @@ coverage.xml .pytest_cache/ # Virtual environments -.env .venv env/ venv/ diff --git a/packages/python-sdk/README.md b/packages/python-sdk/README.md index ec5689908ff..e6c87d3b3a9 100644 --- a/packages/python-sdk/README.md +++ b/packages/python-sdk/README.md @@ -91,7 +91,7 @@ if is_ready: **Returns:** `bool` -##### execute_workflow_sync(workflow_id, input_data=None, timeout=30.0, poll_interval=1.0, max_wait_time=300.0) +##### execute_workflow_sync(workflow_id, input_data=None, timeout=30.0) Execute a workflow and poll for completion (useful for long-running workflows). @@ -99,9 +99,7 @@ Execute a workflow and poll for completion (useful for long-running workflows). result = client.execute_workflow_sync( "workflow-id", input_data={"data": "some input"}, - timeout=60.0, - poll_interval=2.0, - max_wait_time=300.0 + timeout=60.0 ) ``` @@ -109,8 +107,6 @@ result = client.execute_workflow_sync( - `workflow_id` (str): The ID of the workflow to execute - `input_data` (dict, optional): Input data to pass to the workflow - `timeout` (float): Timeout for the initial request in seconds -- `poll_interval` (float): Polling interval in seconds (default: 1.0) -- `max_wait_time` (float): Maximum wait time in seconds (default: 300.0) **Returns:** `WorkflowExecutionResult` @@ -170,6 +166,7 @@ class WorkflowStatus: ```python class SimStudioError(Exception): def __init__(self, message: str, code: Optional[str] = None, status: Optional[int] = None): + super().__init__(message) self.code = code self.status = status ``` @@ -231,6 +228,8 @@ def execute_with_error_handling(): print("Workflow execution timed out") elif error.code == "USAGE_LIMIT_EXCEEDED": print("Usage limit exceeded") + elif error.code == "INVALID_JSON": + print("Invalid JSON in request body") else: print(f"Workflow error: {error}") raise diff --git a/packages/python-sdk/examples/basic_usage.py b/packages/python-sdk/examples/basic_usage.py index 89bcb871780..b4d2dba8b8b 100644 --- a/packages/python-sdk/examples/basic_usage.py +++ b/packages/python-sdk/examples/basic_usage.py @@ -9,10 +9,7 @@ def basic_example(): """Example 1: Basic workflow execution""" - client = SimStudioClient( - api_key=os.getenv("SIMSTUDIO_API_KEY"), - base_url="https://simstudio.ai" - ) + client = SimStudioClient(api_key=os.getenv("SIMSTUDIO_API_KEY")) try: # Execute a workflow without input @@ -53,10 +50,18 @@ def with_input_example(): timeout=60.0 # 60 seconds ) - print(f"Execution result: {result}") + if result.success: + print("āœ… Workflow executed successfully!") + print(f"Output: {result.output}") + if result.metadata: + print(f"Duration: {result.metadata.get('duration')} ms") + else: + print(f"āŒ Workflow failed: {result.error}") + except SimStudioError as error: + print(f"SDK Error: {error} (Code: {error.code})") except Exception as error: - print(f"Error: {error}") + print(f"Unexpected error: {error}") def status_example(): @@ -149,7 +154,14 @@ def error_handling_example(): try: result = client.execute_workflow("your-workflow-id") - return result + + if result.success: + print("āœ… Workflow executed successfully!") + print(f"Output: {result.output}") + return result + else: + print(f"āŒ Workflow failed: {result.error}") + return result except SimStudioError as error: if error.code == "UNAUTHORIZED": print("āŒ Invalid API key") @@ -157,6 +169,8 @@ def error_handling_example(): print("ā±ļø Workflow execution timed out") elif error.code == "USAGE_LIMIT_EXCEEDED": print("šŸ’³ Usage limit exceeded") + elif error.code == "INVALID_JSON": + print("šŸ“ Invalid JSON in request body") elif error.status == 404: print("šŸ” Workflow not found") elif error.status == 403: diff --git a/packages/python-sdk/simstudio/__init__.py b/packages/python-sdk/simstudio/__init__.py index a857828b3e9..8feac084cce 100644 --- a/packages/python-sdk/simstudio/__init__.py +++ b/packages/python-sdk/simstudio/__init__.py @@ -6,7 +6,7 @@ import json import time -from typing import Any, Dict, Optional, Union +from typing import Any, Dict, Optional from dataclasses import dataclass import requests @@ -87,14 +87,11 @@ def execute_workflow( url = f"{self.base_url}/api/workflows/{workflow_id}/execute" try: - if input_data: - response = self._session.post( - url, - json=input_data, - timeout=timeout - ) - else: - response = self._session.get(url, timeout=timeout) + response = self._session.post( + url, + json=input_data or {}, + timeout=timeout + ) if not response.ok: try: @@ -110,7 +107,7 @@ def execute_workflow( result_data = response.json() return WorkflowExecutionResult( - success=result_data.get('success', True), + success=result_data['success'], output=result_data.get('output'), error=result_data.get('error'), logs=result_data.get('logs'), @@ -185,9 +182,7 @@ def execute_workflow_sync( self, workflow_id: str, input_data: Optional[Dict[str, Any]] = None, - timeout: float = 30.0, - poll_interval: float = 1.0, - max_wait_time: float = 300.0 + timeout: float = 30.0 ) -> WorkflowExecutionResult: """ Execute a workflow and poll for completion (useful for long-running workflows). @@ -199,8 +194,6 @@ def execute_workflow_sync( workflow_id: The ID of the workflow to execute input_data: Input data to pass to the workflow timeout: Timeout for the initial request in seconds - poll_interval: Polling interval in seconds (default: 1.0) - max_wait_time: Maximum wait time in seconds (default: 300.0) Returns: WorkflowExecutionResult object containing the execution result diff --git a/packages/python-sdk/tests/test_client.py b/packages/python-sdk/tests/test_client.py index 7130b6d842a..496f5cd8dff 100644 --- a/packages/python-sdk/tests/test_client.py +++ b/packages/python-sdk/tests/test_client.py @@ -45,7 +45,6 @@ def test_set_base_url_strips_trailing_slash(): @patch('simstudio.requests.Session.get') def test_validate_workflow_returns_false_on_error(mock_get): """Test that validate_workflow returns False when request fails.""" - from simstudio import SimStudioError mock_get.side_effect = SimStudioError("Network error") client = SimStudioClient(api_key="test-api-key") diff --git a/packages/ts-sdk/README.md b/packages/ts-sdk/README.md index a05b7ae7689..4ae80f0f1cb 100644 --- a/packages/ts-sdk/README.md +++ b/packages/ts-sdk/README.md @@ -103,19 +103,15 @@ Execute a workflow and poll for completion (useful for long-running workflows). ```typescript const result = await client.executeWorkflowSync('workflow-id', { input: { data: 'some input' }, - timeout: 60000, - pollInterval: 2000, - maxWaitTime: 300000 + timeout: 60000 }); ``` **Parameters:** - `workflowId` (string): The ID of the workflow to execute -- `options` (ExecutionOptions & polling options, optional): +- `options` (ExecutionOptions, optional): - `input` (any): Input data to pass to the workflow - `timeout` (number): Timeout for the initial request in milliseconds - - `pollInterval` (number): Polling interval in milliseconds (default: 1000) - - `maxWaitTime` (number): Maximum wait time in milliseconds (default: 300000) **Returns:** `Promise` @@ -241,6 +237,9 @@ async function executeWithErrorHandling() { case 'USAGE_LIMIT_EXCEEDED': console.error('Usage limit exceeded'); break; + case 'INVALID_JSON': + console.error('Invalid JSON in request body'); + break; default: console.error('Workflow error:', error.message); } diff --git a/packages/ts-sdk/examples/basic-usage.ts b/packages/ts-sdk/examples/basic-usage.ts index c943aa755aa..4b3bee656e9 100644 --- a/packages/ts-sdk/examples/basic-usage.ts +++ b/packages/ts-sdk/examples/basic-usage.ts @@ -49,9 +49,21 @@ async function withInputExample() { timeout: 60000, // 60 seconds }) - console.log('Execution result:', result) + if (result.success) { + console.log('āœ… Workflow executed successfully!') + console.log('Output:', result.output) + if (result.metadata?.duration) { + console.log('Duration:', result.metadata.duration, 'ms') + } + } else { + console.log('āŒ Workflow failed:', result.error) + } } catch (error) { - console.error('Error:', error) + if (error instanceof SimStudioError) { + console.error('SDK Error:', error.message, 'Code:', error.code) + } else { + console.error('Unexpected error:', error) + } } } @@ -78,26 +90,41 @@ async function statusExample() { if (status.isDeployed) { // Execute the workflow const result = await client.executeWorkflow('your-workflow-id') - console.log('Result:', result) + + if (result.success) { + console.log('āœ… Workflow executed successfully!') + console.log('Output:', result.output) + } else { + console.log('āŒ Workflow failed:', result.error) + } } } catch (error) { - console.error('Error:', error) + if (error instanceof SimStudioError) { + console.error('SDK Error:', error.message, 'Code:', error.code) + } else { + console.error('Unexpected error:', error) + } } } // Run examples if (require.main === module) { - console.log('šŸš€ Running Sim Studio SDK Examples\n') + async function runExamples() { + console.log('šŸš€ Running Sim Studio SDK Examples\n') + + try { + await basicExample() + console.log('\nāœ… Basic example completed') - basicExample() - .then(() => console.log('\nāœ… Basic example completed')) - .catch(console.error) + await withInputExample() + console.log('\nāœ… Input example completed') - withInputExample() - .then(() => console.log('\nāœ… Input example completed')) - .catch(console.error) + await statusExample() + console.log('\nāœ… Status example completed') + } catch (error) { + console.error('Error running examples:', error) + } + } - statusExample() - .then(() => console.log('\nāœ… Status example completed')) - .catch(console.error) + runExamples() } diff --git a/packages/ts-sdk/src/index.test.ts b/packages/ts-sdk/src/index.test.ts index 987c2b661d9..49f424a9c0f 100644 --- a/packages/ts-sdk/src/index.test.ts +++ b/packages/ts-sdk/src/index.test.ts @@ -36,6 +36,8 @@ describe('SimStudioClient', () => { // Verify the method exists expect(client.setApiKey).toBeDefined() + // Verify the API key was actually updated + expect((client as any).apiKey).toBe(newApiKey) }) }) @@ -49,7 +51,8 @@ describe('SimStudioClient', () => { it('should strip trailing slash from base URL', () => { const urlWithSlash = 'https://test.simstudio.ai/' client.setBaseUrl(urlWithSlash) - expect(client.setBaseUrl).toBeDefined() + // Verify the trailing slash was actually stripped + expect((client as any).baseUrl).toBe('https://test.simstudio.ai') }) }) @@ -61,6 +64,23 @@ describe('SimStudioClient', () => { const result = await client.validateWorkflow('test-workflow-id') expect(result).toBe(false) }) + + it('should return true when workflow is deployed', async () => { + const fetch = await import('node-fetch') + const mockResponse = { + ok: true, + json: vi.fn().mockResolvedValue({ + isDeployed: true, + deployedAt: '2023-01-01T00:00:00Z', + isPublished: false, + needsRedeployment: false, + }), + } + vi.mocked(fetch.default).mockResolvedValue(mockResponse as any) + + const result = await client.validateWorkflow('test-workflow-id') + expect(result).toBe(true) + }) }) }) diff --git a/packages/ts-sdk/src/index.ts b/packages/ts-sdk/src/index.ts index 8225801ecac..648347f76c0 100644 --- a/packages/ts-sdk/src/index.ts +++ b/packages/ts-sdk/src/index.ts @@ -49,7 +49,7 @@ export class SimStudioClient { constructor(config: SimStudioConfig) { this.apiKey = config.apiKey - this.baseUrl = config.baseUrl || 'https://simstudio.ai' + this.baseUrl = (config.baseUrl || 'https://simstudio.ai').replace(/\/+$/, '') } /** @@ -69,12 +69,12 @@ export class SimStudioClient { }) const fetchPromise = fetch(url, { - method: input ? 'POST' : 'GET', + method: 'POST', headers: { 'Content-Type': 'application/json', 'X-API-Key': this.apiKey, }, - body: input ? JSON.stringify(input) : undefined, + body: JSON.stringify(input || {}), }) const response = await Promise.race([fetchPromise, timeoutPromise]) @@ -142,13 +142,11 @@ export class SimStudioClient { */ async executeWorkflowSync( workflowId: string, - options: ExecutionOptions & { pollInterval?: number; maxWaitTime?: number } = {} + options: ExecutionOptions = {} ): Promise { - const { pollInterval = 1000, maxWaitTime = 300000, ...executeOptions } = options - // For now, the API is synchronous, so we just execute directly // In the future, if async execution is added, this method can be enhanced - return this.executeWorkflow(workflowId, executeOptions) + return this.executeWorkflow(workflowId, options) } /** @@ -174,7 +172,7 @@ export class SimStudioClient { * Set a new base URL */ setBaseUrl(baseUrl: string): void { - this.baseUrl = baseUrl + this.baseUrl = baseUrl.replace(/\/+$/, '') } } From 9561dbb01678fc785fa7f942677592b4254862f1 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 20 Jun 2025 11:10:39 -0700 Subject: [PATCH 4/4] improvements --- apps/docs/content/docs/sdks/python.mdx | 6 +-- apps/docs/content/docs/sdks/typescript.mdx | 7 ++- packages/python-sdk/README.md | 48 ++++++++++++++++++++- packages/python-sdk/examples/basic_usage.py | 9 +++- packages/python-sdk/pyproject.toml | 1 + packages/python-sdk/simstudio/__init__.py | 2 - packages/python-sdk/tests/test_client.py | 7 ++- packages/ts-sdk/README.md | 44 +++++++++++++++++++ packages/ts-sdk/package.json | 8 ++++ packages/ts-sdk/src/index.test.ts | 19 +++++++- packages/ts-sdk/src/index.ts | 4 +- 11 files changed, 142 insertions(+), 13 deletions(-) diff --git a/apps/docs/content/docs/sdks/python.mdx b/apps/docs/content/docs/sdks/python.mdx index ecf4b2d7c59..277080da7b2 100644 --- a/apps/docs/content/docs/sdks/python.mdx +++ b/apps/docs/content/docs/sdks/python.mdx @@ -74,7 +74,7 @@ result = client.execute_workflow( **Parameters:** - `workflow_id` (str): The ID of the workflow to execute - `input_data` (dict, optional): Input data to pass to the workflow -- `timeout` (float): Timeout in seconds (default: 30.0) +- `timeout` (float, optional): Timeout in seconds (default: 30.0) **Returns:** `WorkflowExecutionResult` @@ -165,9 +165,9 @@ class WorkflowExecutionResult: success: bool output: Optional[Any] = None error: Optional[str] = None - logs: Optional[list] = None + logs: Optional[List[Any]] = None metadata: Optional[Dict[str, Any]] = None - trace_spans: Optional[list] = None + trace_spans: Optional[List[Any]] = None total_duration: Optional[float] = None ``` diff --git a/apps/docs/content/docs/sdks/typescript.mdx b/apps/docs/content/docs/sdks/typescript.mdx index a831c047ce2..6fb4bf4f7c8 100644 --- a/apps/docs/content/docs/sdks/typescript.mdx +++ b/apps/docs/content/docs/sdks/typescript.mdx @@ -309,8 +309,13 @@ Configure the client using environment variables: import { SimStudioClient } from 'simstudio-ts-sdk'; // Development configuration + const apiKey = process.env.SIMSTUDIO_API_KEY; + if (!apiKey) { + throw new Error('SIMSTUDIO_API_KEY environment variable is required'); + } + const client = new SimStudioClient({ - apiKey: process.env.SIMSTUDIO_API_KEY!, + apiKey, baseUrl: process.env.SIMSTUDIO_BASE_URL // optional }); ``` diff --git a/packages/python-sdk/README.md b/packages/python-sdk/README.md index e6c87d3b3a9..c85b55b80cc 100644 --- a/packages/python-sdk/README.md +++ b/packages/python-sdk/README.md @@ -11,11 +11,12 @@ pip install simstudio-sdk ## Quick Start ```python +import os from simstudio import SimStudioClient # Initialize the client client = SimStudioClient( - api_key="your-api-key-here", + api_key=os.getenv("SIMSTUDIO_API_KEY", "your-api-key-here"), base_url="https://simstudio.ai" # optional, defaults to https://simstudio.ai ) @@ -319,6 +320,51 @@ for result in results: 4. Select or create an API key during the deployment process 5. Copy the API key to use in your application +## Development + +### Running Tests + +To run the tests locally: + +1. Clone the repository and navigate to the Python SDK directory: + ```bash + cd packages/python-sdk + ``` + +2. Create and activate a virtual environment: + ```bash + python3 -m venv venv + source venv/bin/activate # On Windows: venv\Scripts\activate + ``` + +3. Install the package in development mode with test dependencies: + ```bash + pip install -e ".[dev]" + ``` + +4. Run the tests: + ```bash + pytest tests/ -v + ``` + +### Code Quality + +Run code quality checks: + +```bash +# Code formatting +black simstudio/ + +# Linting +flake8 simstudio/ --max-line-length=100 + +# Type checking +mypy simstudio/ + +# Import sorting +isort simstudio/ +``` + ## Requirements - Python 3.8+ diff --git a/packages/python-sdk/examples/basic_usage.py b/packages/python-sdk/examples/basic_usage.py index b4d2dba8b8b..562a4a266b5 100644 --- a/packages/python-sdk/examples/basic_usage.py +++ b/packages/python-sdk/examples/basic_usage.py @@ -132,13 +132,20 @@ def batch_execution_example(): status = "āœ… Success" if result.success else "āŒ Failed" print(f"{status}: {workflow_id}") + except SimStudioError as error: + results.append({ + "workflow_id": workflow_id, + "success": False, + "error": str(error) + }) + print(f"āŒ SDK Error in {workflow_id}: {error}") except Exception as error: results.append({ "workflow_id": workflow_id, "success": False, "error": str(error) }) - print(f"āŒ Error in {workflow_id}: {error}") + print(f"āŒ Unexpected error in {workflow_id}: {error}") # Summary successful = sum(1 for r in results if r["success"]) diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 1b725b404d5..728c507f145 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -41,6 +41,7 @@ dev = [ "flake8>=4.0.0", "mypy>=0.910", "isort>=5.0.0", + "types-requests>=2.25.0", ] [project.urls] diff --git a/packages/python-sdk/simstudio/__init__.py b/packages/python-sdk/simstudio/__init__.py index 8feac084cce..767d9372c35 100644 --- a/packages/python-sdk/simstudio/__init__.py +++ b/packages/python-sdk/simstudio/__init__.py @@ -4,8 +4,6 @@ Official Python SDK for Sim Studio, allowing you to execute workflows programmatically. """ -import json -import time from typing import Any, Dict, Optional from dataclasses import dataclass diff --git a/packages/python-sdk/tests/test_client.py b/packages/python-sdk/tests/test_client.py index 496f5cd8dff..64da5dac129 100644 --- a/packages/python-sdk/tests/test_client.py +++ b/packages/python-sdk/tests/test_client.py @@ -51,6 +51,7 @@ def test_validate_workflow_returns_false_on_error(mock_get): result = client.validate_workflow("test-workflow-id") assert result is False + mock_get.assert_called_once_with("https://simstudio.ai/api/workflows/test-workflow-id/status") def test_simstudio_error(): @@ -87,8 +88,10 @@ def test_workflow_status(): assert status.needs_redeployment is False -def test_context_manager(): +@patch('simstudio.requests.Session.close') +def test_context_manager(mock_close): """Test SimStudioClient as context manager.""" with SimStudioClient(api_key="test-api-key") as client: assert client.api_key == "test-api-key" - # Should close without error \ No newline at end of file + # Should close without error + mock_close.assert_called_once() \ No newline at end of file diff --git a/packages/ts-sdk/README.md b/packages/ts-sdk/README.md index 4ae80f0f1cb..f35d6fc772f 100644 --- a/packages/ts-sdk/README.md +++ b/packages/ts-sdk/README.md @@ -269,6 +269,50 @@ const client = new SimStudioClient({ 4. Select or create an API key during the deployment process 5. Copy the API key to use in your application +## Development + +### Running Tests + +To run the tests locally: + +1. Clone the repository and navigate to the TypeScript SDK directory: + ```bash + cd packages/ts-sdk + ``` + +2. Install dependencies: + ```bash + bun install + ``` + +3. Run the tests: + ```bash + bun run test + ``` + +### Building + +Build the TypeScript SDK: + +```bash +bun run build +``` + +This will compile TypeScript files to JavaScript and generate type declarations in the `dist/` directory. + +### Development Mode + +For development with auto-rebuild: + +```bash +bun run dev +``` + +## Requirements + +- Node.js 18+ +- TypeScript 5.0+ (for TypeScript projects) + ## License Apache-2.0 \ No newline at end of file diff --git a/packages/ts-sdk/package.json b/packages/ts-sdk/package.json index eccd1253316..c255870d1ff 100644 --- a/packages/ts-sdk/package.json +++ b/packages/ts-sdk/package.json @@ -3,7 +3,15 @@ "version": "0.1.0", "description": "Sim Studio SDK - Execute workflows programmatically", "main": "dist/index.js", + "module": "dist/index.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.js" + } + }, "scripts": { "build": "bun run build:tsc", "build:tsc": "tsc", diff --git a/packages/ts-sdk/src/index.test.ts b/packages/ts-sdk/src/index.test.ts index 49f424a9c0f..afdbb72c847 100644 --- a/packages/ts-sdk/src/index.test.ts +++ b/packages/ts-sdk/src/index.test.ts @@ -45,7 +45,7 @@ describe('SimStudioClient', () => { it('should update the base URL', () => { const newBaseUrl = 'https://new.simstudio.ai' client.setBaseUrl(newBaseUrl) - expect(client.setBaseUrl).toBeDefined() + expect((client as any).baseUrl).toBe(newBaseUrl) }) it('should strip trailing slash from base URL', () => { @@ -81,6 +81,23 @@ describe('SimStudioClient', () => { const result = await client.validateWorkflow('test-workflow-id') expect(result).toBe(true) }) + + it('should return false when workflow is not deployed', async () => { + const fetch = await import('node-fetch') + const mockResponse = { + ok: true, + json: vi.fn().mockResolvedValue({ + isDeployed: false, + deployedAt: null, + isPublished: false, + needsRedeployment: true, + }), + } + vi.mocked(fetch.default).mockResolvedValue(mockResponse as any) + + const result = await client.validateWorkflow('test-workflow-id') + expect(result).toBe(false) + }) }) }) diff --git a/packages/ts-sdk/src/index.ts b/packages/ts-sdk/src/index.ts index 648347f76c0..9181e5a63d3 100644 --- a/packages/ts-sdk/src/index.ts +++ b/packages/ts-sdk/src/index.ts @@ -80,7 +80,7 @@ export class SimStudioClient { const response = await Promise.race([fetchPromise, timeoutPromise]) if (!response.ok) { - const errorData = (await response.json().catch(() => ({}))) as any + const errorData = (await response.json().catch(() => ({}))) as unknown as any throw new SimStudioError( errorData.error || `HTTP ${response.status}: ${response.statusText}`, errorData.code, @@ -118,7 +118,7 @@ export class SimStudioClient { }) if (!response.ok) { - const errorData = (await response.json().catch(() => ({}))) as any + const errorData = (await response.json().catch(() => ({}))) as unknown as any throw new SimStudioError( errorData.error || `HTTP ${response.status}: ${response.statusText}`, errorData.code,