Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 202 additions & 0 deletions .github/workflows/publish-sdk.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
name: Publish SDK Package

on:
workflow_dispatch:
inputs:
release_train_version:
description: "Human-facing SDK release train version, for example 0.1.0"
required: true
type: string
package:
description: "Package to build or publish"
required: true
type: choice
options:
- python
- typescript
- go
- java
- dotnet
package_version:
description: "Language package version. Can be a patch above the train version."
required: true
type: string
channel:
description: "Registry channel / dist-tag"
required: true
default: preview
type: choice
options:
- preview
- next
- beta
- latest
dry_run:
description: "Build and validate only; do not publish to registries."
required: true
default: true
type: boolean

jobs:
publish-sdk:
name: ${{ inputs.package }} ${{ inputs.package_version }}
runs-on: ubuntu-latest
permissions:
contents: write
packages: write

steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
if: inputs.package == 'python'
uses: actions/setup-python@v5
with:
python-version: "3.13"
cache: pip
cache-dependency-path: python/pyproject.toml

- name: Set up Node.js
if: inputs.package == 'typescript'
uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
cache: npm
cache-dependency-path: typescript/package-lock.json

- name: Set up Go
if: inputs.package == 'go'
uses: actions/setup-go@v5
with:
go-version: "1.22"

- name: Set up Java
if: inputs.package == 'java'
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "17"
cache: maven
server-id: github
server-username: GITHUB_ACTOR
server-password: GITHUB_TOKEN

- name: Set up .NET
if: inputs.package == 'dotnet'
uses: actions/setup-dotnet@v4
with:
dotnet-version: "8.0.x"

- name: Validate versions
run: |
python - <<'PY'
import os
import re
for name in ("release_train_version", "package_version"):
value = os.environ[f"INPUT_{name.upper()}"]
if not re.fullmatch(r"\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?", value):
raise SystemExit(f"{name} must be SemVer-like, got {value!r}")
PY
env:
INPUT_RELEASE_TRAIN_VERSION: ${{ inputs.release_train_version }}
INPUT_PACKAGE_VERSION: ${{ inputs.package_version }}

- name: Run SDK PR quality gates
run: bash scripts/ci/pr_checks.sh

- name: Build Python package
if: inputs.package == 'python'
run: |
python -m pip install --upgrade build twine
python - <<'PY'
from pathlib import Path
path = Path("python/pyproject.toml")
text = path.read_text()
text = text.replace('version = "0.1.0"', f'version = "${{ inputs.package_version }}"')
path.write_text(text)
PY
python -m build python
python -m twine check python/dist/*

- name: Publish Python package
if: inputs.package == 'python' && inputs.dry_run == false
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: python -m twine upload python/dist/*

- name: Build TypeScript package
if: inputs.package == 'typescript'
working-directory: typescript
run: |
npm ci
npm version "${{ inputs.package_version }}" --no-git-tag-version
npm run build
npm pack

- name: Publish TypeScript package
if: inputs.package == 'typescript' && inputs.dry_run == false
working-directory: typescript
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --access public --tag "${{ inputs.channel }}"

- name: Verify Go module
if: inputs.package == 'go'
working-directory: go
run: |
go test ./...
go list ./...
echo "Go submodule tag for this package is go/v${{ inputs.package_version }}"

- name: Create Go module tag
if: inputs.package == 'go' && inputs.dry_run == false
run: |
tag="go/v${{ inputs.package_version }}"
git tag "$tag"
git push origin "$tag"

- name: Build Java package
if: inputs.package == 'java'
working-directory: java
run: |
mvn -B versions:set -DnewVersion="${{ inputs.package_version }}" -DgenerateBackupPoms=false
mvn -B package

- name: Publish Java package to GitHub Packages
if: inputs.package == 'java' && inputs.dry_run == false
working-directory: java
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: mvn -B deploy -DskipTests

- name: Build .NET package
if: inputs.package == 'dotnet'
run: |
dotnet restore dotnet/src/SynapseNetwork.Sdk/SynapseNetwork.Sdk.csproj
dotnet test dotnet/tests/SynapseNetwork.Sdk.Tests/SynapseNetwork.Sdk.Tests.csproj
dotnet pack dotnet/src/SynapseNetwork.Sdk/SynapseNetwork.Sdk.csproj \
-c Release \
-p:Version="${{ inputs.package_version }}" \
-o dotnet/artifacts

- name: Publish .NET package
if: inputs.package == 'dotnet' && inputs.dry_run == false
run: |
dotnet nuget push dotnet/artifacts/*.nupkg \
--api-key "${{ secrets.NUGET_API_KEY }}" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate

- name: Summary
run: |
echo "### SDK package workflow" >> "$GITHUB_STEP_SUMMARY"
echo "- Train: ${{ inputs.release_train_version }}" >> "$GITHUB_STEP_SUMMARY"
echo "- Package: ${{ inputs.package }}" >> "$GITHUB_STEP_SUMMARY"
echo "- Version: ${{ inputs.package_version }}" >> "$GITHUB_STEP_SUMMARY"
echo "- Channel: ${{ inputs.channel }}" >> "$GITHUB_STEP_SUMMARY"
echo "- Dry run: ${{ inputs.dry_run }}" >> "$GITHUB_STEP_SUMMARY"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,8 @@ target/
# Local agent state
.agents-memory/

# Local E2E evidence
output/

# macOS
.DS_Store
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ Developers should pass staging before any future production migration. Productio

| Mode | Use for | SDK method | Required cost parameter | Billing result |
|---|---|---|---|---|
| Fixed-price API invoke | Normal API services discovered from the marketplace | Python `invoke()` / TypeScript `invoke()` | Pass latest discovery price as `cost_usdc` / `costUsdc` | Gateway rejects with `PRICE_MISMATCH` if the live price changed |
| Token-metered LLM invoke | LLM services registered with `serviceKind=llm` and `priceModel=token_metered` | Python `invoke_llm()` / TypeScript `invokeLlm()` | Do not pass `cost_usdc` / `costUsdc`; optional cap is `max_cost_usdc` / `maxCostUsdc` | Gateway holds a cap, then charges final provider-reported token usage |
| Fixed-price API invoke | Normal API services discovered from the marketplace | Python/TypeScript `invoke()`, Go `Invoke()`, Java `invoke()`, .NET `InvokeAsync()` | Pass latest discovery price as `cost_usdc` / `costUsdc` / `CostUSDC` / `CostUsdc` | Gateway rejects with `PRICE_MISMATCH` if the live price changed |
| Token-metered LLM invoke | LLM services registered with `serviceKind=llm` and `priceModel=token_metered` | Python `invoke_llm()`, TypeScript `invokeLlm()`, Go `InvokeLLM()`, Java `invokeLlm()`, .NET `InvokeLlmAsync()` | Do not pass fixed-price cost; optional cap is `max_cost_usdc` / `maxCostUsdc` / `MaxCostUSDC` / `MaxCostUsdc` | Gateway holds a cap, then charges final provider-reported token usage |

Do not recompute money with floating-point math. Pass discovered prices and spend caps through exactly; prefer string amounts such as `"0.05"` when the SDK method accepts strings.

Expand Down Expand Up @@ -107,17 +107,17 @@ Provider is an owner-scoped supply-side role, not a separate root account. Keep
|---|---|---|
| Python | `python/` | Consumer, owner auth, provider publishing |
| TypeScript | `typescript/` | Consumer, owner auth, provider publishing |
| Go | `go/` | Consumer runtime preview |
| Java/JVM | `java/` | Consumer runtime preview; Kotlin can call the Java SDK |
| .NET | `dotnet/` | Consumer runtime preview |
| Go | `go/` | Consumer, owner auth, provider publishing |
| Java/JVM | `java/` | Consumer, owner auth, provider publishing; Kotlin can call the Java SDK |
| .NET | `dotnet/` | Consumer, owner auth, provider publishing |

Wave 1 multi-language SDKs focus on agent runtime: search/discover, fixed-price invoke, token-metered LLM invoke, receipt lookup, and gateway health. Owner auth and provider publishing for Go, Java, and .NET are planned after the consumer runtime surface is stable.
All five SDKs expose the same public capability families: agent runtime, owner wallet auth, credential management, balance/deposit helpers, usage/finance reads, and provider lifecycle/withdrawal helpers. The exact naming follows each language's conventions, but the Gateway contract and money rules stay consistent.

## Examples By SDK

All runnable examples default to staging and read `SYNAPSE_AGENT_KEY`.

| SDK | Free fixed-price smoke | LLM smoke | Full local E2E |
| SDK | Free fixed-price smoke | LLM smoke | Full E2E |
|---|---|---|---|
| Python | `PYTHONPATH=python python3 python/examples/free_service_smoke.py` | `PYTHONPATH=python python3 python/examples/llm_smoke.py` | `PYTHONPATH=python python3 python/examples/e2e.py` |
| TypeScript | `npm run example:free --prefix typescript` | `npm run example:llm --prefix typescript` | `npm run example:e2e --prefix typescript` |
Expand All @@ -128,10 +128,13 @@ All runnable examples default to staging and read `SYNAPSE_AGENT_KEY`.
To run every SDK through the same real Gateway E2E harness:

```bash
export SYNAPSE_OWNER_PRIVATE_KEY=0x...
export SYNAPSE_AGENT_KEY=agt_xxx
bash scripts/e2e/sdk_wave1_local.sh --skip-install
bash scripts/e2e/sdk_parity_e2e.sh --env staging --skip-install
```

For a private gateway test target, use `--env local` with an explicit `SYNAPSE_GATEWAY_URL`; `local` is not a public SDK environment preset.

## Agent Quickstart: Python

Step 1: get your Agent Key from the Synapse Gateway Dashboard.
Expand Down
19 changes: 11 additions & 8 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ SynapseNetwork 让 Agent 可以发现服务、通过 gateway 调用服务,并

| 模式 | 适用场景 | SDK 方法 | 必传费用参数 | 计费结果 |
|---|---|---|---|---|
| Fixed-price API invoke | marketplace 发现的普通 API 服务 | Python `invoke()` / TypeScript `invoke()` | 传最新 discovery price:`cost_usdc` / `costUsdc` | 如果 live price 已变化,Gateway 用 `PRICE_MISMATCH` 拒绝 |
| Token-metered LLM invoke | `serviceKind=llm` 且 `priceModel=token_metered` 的 LLM 服务 | Python `invoke_llm()` / TypeScript `invokeLlm()` | 不传 `cost_usdc` / `costUsdc`;可选上限是 `max_cost_usdc` / `maxCostUsdc` | Gateway 先冻结上限,再按 Provider 返回的 final token usage 扣费 |
| Fixed-price API invoke | marketplace 发现的普通 API 服务 | Python/TypeScript `invoke()`、Go `Invoke()`、Java `invoke()`、.NET `InvokeAsync()` | 传最新 discovery price:`cost_usdc` / `costUsdc` / `CostUSDC` / `CostUsdc` | 如果 live price 已变化,Gateway 用 `PRICE_MISMATCH` 拒绝 |
| Token-metered LLM invoke | `serviceKind=llm` 且 `priceModel=token_metered` 的 LLM 服务 | Python `invoke_llm()`、TypeScript `invokeLlm()`、Go `InvokeLLM()`、Java `invokeLlm()`、.NET `InvokeLlmAsync()` | 不传 fixed-price cost;可选上限是 `max_cost_usdc` / `maxCostUsdc` / `MaxCostUSDC` / `MaxCostUsdc` | Gateway 先冻结上限,再按 Provider 返回的 final token usage 扣费 |

不要用浮点数重新计算金额。调用时传 discovery 得到的价格或预算上限;SDK 方法支持时优先使用字符串金额,例如 `"0.05"`。

Expand Down Expand Up @@ -107,17 +107,17 @@ Provider 是 owner scope 下的供给侧角色,不是第二套根账户体系
|---|---|---|
| Python | `python/` | Consumer、owner auth、provider publishing |
| TypeScript | `typescript/` | Consumer、owner auth、provider publishing |
| Go | `go/` | Consumer runtime preview |
| Java/JVM | `java/` | Consumer runtime preview;Kotlin 可直接调用 Java SDK |
| .NET | `dotnet/` | Consumer runtime preview |
| Go | `go/` | Consumer、owner auth、provider publishing |
| Java/JVM | `java/` | Consumer、owner auth、provider publishing;Kotlin 可直接调用 Java SDK |
| .NET | `dotnet/` | Consumer、owner auth、provider publishing |

Wave 1 多语言 SDK 先聚焦 Agent runtime:search/discover、fixed-price invoke、token-metered LLM invoke、receipt lookup 和 gateway health。Go、Java、.NET 的 owner auth 与 provider publishing 会在 consumer runtime 稳定后进入下一阶段
五种 SDK 都覆盖同一组公开能力:Agent runtime、owner wallet auth、credential 管理、balance/deposit helper、usage/finance 读取,以及 provider lifecycle/withdrawal helper。方法命名遵循各语言习惯,但 Gateway contract 和金额规则保持一致

## 各 SDK 示例

所有 runnable examples 默认使用 staging,并读取 `SYNAPSE_AGENT_KEY`。

| SDK | 免费 fixed-price smoke | LLM smoke | 完整本地 E2E |
| SDK | 免费 fixed-price smoke | LLM smoke | 完整 E2E |
|---|---|---|---|
| Python | `PYTHONPATH=python python3 python/examples/free_service_smoke.py` | `PYTHONPATH=python python3 python/examples/llm_smoke.py` | `PYTHONPATH=python python3 python/examples/e2e.py` |
| TypeScript | `npm run example:free --prefix typescript` | `npm run example:llm --prefix typescript` | `npm run example:e2e --prefix typescript` |
Expand All @@ -128,10 +128,13 @@ Wave 1 多语言 SDK 先聚焦 Agent runtime:search/discover、fixed-price inv
统一跑全部 SDK:

```bash
export SYNAPSE_OWNER_PRIVATE_KEY=0x...
export SYNAPSE_AGENT_KEY=agt_xxx
bash scripts/e2e/sdk_wave1_local.sh --skip-install
bash scripts/e2e/sdk_parity_e2e.sh --env staging --skip-install
```

如需测试私有 gateway,使用 `--env local` 并显式设置 `SYNAPSE_GATEWAY_URL`;`local` 不是公开 SDK environment preset。

## Agent 快速接入:Python

步骤 1:从 Synapse Gateway Dashboard 获取 Agent Key。
Expand Down
5 changes: 3 additions & 2 deletions docs/agent-map/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
"Canonical runtime flow is discovery/search plus one of two invoke modes: fixed-price invoke with costUsdc/cost_usdc, or token-metered LLM invoke with maxCostUsdc/max_cost_usdc.",
"Public examples should use SYNAPSE_AGENT_KEY for agent runtime credentials; SYNAPSE_API_KEY is only a Python legacy fallback.",
"Public preview examples target staging on Arbitrum Sepolia with MockUSDC test assets.",
"Go, Java, and .NET are Wave 1 Consumer SDKs only; owner auth and provider lifecycle helpers remain Python/TypeScript until Phase 2.",
"Go, Java, and .NET now expose owner auth, credential, finance, provider lifecycle, and withdrawal helpers alongside the existing runtime client.",
"Do not restore old quote-first gateway calls."
]
},
Expand Down Expand Up @@ -268,6 +268,7 @@
"dotnet/examples/free-service-smoke/Program.cs",
"dotnet/examples/llm-smoke/Program.cs",
"dotnet/examples/e2e/Program.cs",
"scripts/e2e/sdk_parity_e2e.sh",
"scripts/e2e/sdk_wave1_local.sh",
"docs/test/consumer-e2e-plan.md",
"docs/test/typescript-provider-onboarding-e2e-plan.md",
Expand All @@ -289,7 +290,7 @@
"bash scripts/ci/go_checks.sh",
"bash scripts/ci/java_checks.sh",
"bash scripts/ci/dotnet_checks.sh",
"SYNAPSE_AGENT_KEY=agt_xxx bash scripts/e2e/sdk_wave1_local.sh --skip-install"
"SYNAPSE_OWNER_PRIVATE_KEY=0x... SYNAPSE_AGENT_KEY=agt_xxx bash scripts/e2e/sdk_parity_e2e.sh --env staging --skip-install"
],
"notes": [
"Provider staging examples require a public HTTPS endpoint reachable by the staging gateway.",
Expand Down
9 changes: 7 additions & 2 deletions docs/ops/release-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Use this checklist for SDK releases and public documentation updates.
## Preflight

- [ ] `bash scripts/ci/pr_checks.sh` passes.
- [ ] Read `docs/ops/sdk-release-runbook.md`.
- [ ] `.github/workflows/ci.yml` and `.github/workflows/pr-ci.yml` are current.
- [ ] 更新 CHANGELOG.md with the release date and developer-visible changes.
- [ ] Public examples use `SYNAPSE_AGENT_KEY`.
Expand All @@ -27,5 +28,9 @@ Do not switch public examples from staging to production until production DNS, g

## Publish

- [ ] Create and push the Git tag.
- [ ] Publish the GitHub Release with install notes and staging/prod status.
- [ ] Initialize the SDK release train in Synapse-Network-Growing `/releases` -> `SDK Packages`.
- [ ] Dry-run each selected package through `.github/workflows/publish-sdk.yml`.
- [ ] Publish each selected package through `.github/workflows/publish-sdk.yml`.
- [ ] For Go, verify the subdirectory module tag uses `go/vX.Y.Z`.
- [ ] Publish the GitHub Release with package URLs, install notes, and public-preview status.
- [ ] Do not describe SDK packages as staging/prod deployments; SDK packages only have registry channels.
Loading
Loading