From c6900a15ca44fca7525b1ac84c6333040ac59e6c Mon Sep 17 00:00:00 2001 From: cc-mac-mini Date: Sat, 2 May 2026 23:24:06 +0800 Subject: [PATCH] Use synapse-network-ai npm scope --- README.md | 23 ++++---- README.zh-CN.md | 12 ++-- docs/guides/getting-started.md | 2 +- docs/ops/sdk-release-runbook.md | 5 +- docs/sdk/README.md | 4 +- docs/sdk/README.zh-CN.md | 4 +- docs/sdk/dotnet_integration.md | 8 ++- docs/sdk/go_integration.md | 7 ++- docs/sdk/java_integration.md | 7 ++- docs/sdk/python_integration.md | 2 +- docs/sdk/typescript_integration.md | 5 +- docs/sdk/typescript_provider_integration.md | 2 +- dotnet/examples/e2e/Program.cs | 27 +++++++-- dotnet/examples/free-service-smoke/Program.cs | 57 +++++++++++++++---- go/examples/e2e/main.go | 26 +++++++-- go/examples/free_service_smoke/main.go | 57 +++++++++++++++---- .../sdk/examples/ExampleSupport.java | 20 ++++++- .../sdk/examples/FreeServiceSmoke.java | 18 ++---- llm-instructions.md | 4 +- llms.txt | 5 +- python/examples/e2e.py | 17 +++++- python/examples/free_service_smoke.py | 28 +++++++-- scripts/ci/repo_hygiene_checks.sh | 8 +++ scripts/e2e/sdk_local_evidence.sh | 4 +- scripts/e2e/sdk_wave1_local.sh | 2 +- typescript/examples/_shared.ts | 18 +++++- typescript/package-lock.json | 4 +- typescript/package.json | 5 +- 28 files changed, 285 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index ae20e9f..9a53305 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ Recommended path: 1. Connect a wallet in the staging Gateway Dashboard. 2. Get or issue an Agent Key and export it as `SYNAPSE_AGENT_KEY`. 3. Set `SYNAPSE_ENV=staging`. -4. Use free services first, or fund staging balance with MockUSDC before paid test calls. +4. Use the free first-party `svc_synapse_echo` smoke service first, or fund staging balance with MockUSDC before paid test calls. 5. Run one fixed-price API invoke and one token-metered LLM invoke. 6. Read the invocation receipt and verify settlement metadata. @@ -55,7 +55,7 @@ Developers should pass staging before any future production migration. Productio | Goal | Use | |---|---| -| Connect SynapseNetwork to an agent framework such as Cursor, Claude Desktop, or LangChain | Official MCP server: `@synapse-network/mcp-server` with `SYNAPSE_AGENT_KEY=agt_xxx` | +| Connect SynapseNetwork to an agent framework such as Cursor, Claude Desktop, or LangChain | Official MCP server: `@synapse-network-ai/mcp-server` with `SYNAPSE_AGENT_KEY=agt_xxx` | | Write application code that invokes services directly | This SDK with `SynapseClient` | | Issue Agent Keys or publish provider APIs | Advanced owner/provider APIs: `SynapseAuth` and `auth.provider()` | @@ -118,6 +118,7 @@ All five SDKs expose the same public capability families: agent runtime, owner w ## Examples By SDK All runnable examples default to staging and read `SYNAPSE_AGENT_KEY`. +The fixed-price smoke examples first call `svc_synapse_echo`, then fall back to another free fixed-price service if echo is unavailable. | SDK | Free fixed-price smoke | LLM smoke | Full E2E | |---|---|---|---| @@ -156,12 +157,12 @@ from synapse_client import SynapseClient client = SynapseClient() -services = client.search("free", limit=10) +services = client.search("svc_synapse_echo", limit=10) service = services[0] result = client.invoke( service.service_id, - {"prompt": "hello"}, + {"message": "hello from Synapse SDK smoke", "metadata": {"scenario": "quickstart"}}, cost_usdc=str(service.price_usdc), idempotency_key="agent-job-001", ) @@ -180,12 +181,14 @@ Step 1: get your Agent Key from the Synapse Gateway Dashboard. Step 2: pass the key to the SDK. +The npm organization is `synapse-network-ai`; use the `@synapse-network-ai/*` scope for official packages. + ```bash -npm install @synapse-network/sdk +npm install @synapse-network-ai/sdk ``` ```ts -import { SynapseClient } from "@synapse-network/sdk"; +import { SynapseClient } from "@synapse-network-ai/sdk"; const agentKey = process.env.SYNAPSE_AGENT_KEY; if (!agentKey) { @@ -197,14 +200,14 @@ const client = new SynapseClient({ environment: "staging", }); -const services = await client.search("free", { +const services = await client.search("svc_synapse_echo", { limit: 10, }); const service = services[0]; const result = await client.invoke( service.serviceId ?? service.id!, - { prompt: "hello" }, + { message: "hello from Synapse SDK smoke", metadata: { scenario: "quickstart" } }, { costUsdc: String(service.pricing?.amount ?? "0"), idempotencyKey: "agent-job-001", @@ -267,7 +270,7 @@ TypeScript: ```ts import { Wallet } from "ethers"; -import { SynapseAuth } from "@synapse-network/sdk"; +import { SynapseAuth } from "@synapse-network-ai/sdk"; const wallet = new Wallet(process.env.OWNER_PRIVATE_KEY!); const auth = SynapseAuth.fromWallet(wallet, { environment: "staging" }); @@ -306,7 +309,7 @@ TypeScript: ```ts import { Wallet } from "ethers"; -import { SynapseAuth } from "@synapse-network/sdk"; +import { SynapseAuth } from "@synapse-network-ai/sdk"; const auth = SynapseAuth.fromWallet(new Wallet(process.env.OWNER_PRIVATE_KEY!), { environment: "staging", diff --git a/README.zh-CN.md b/README.zh-CN.md index 2bafd85..a2d41e8 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -53,7 +53,7 @@ SynapseNetwork 让 Agent 可以发现服务、通过 gateway 调用服务,并 | 目标 | 使用 | |---|---| -| 把 SynapseNetwork 接入 Cursor、Claude Desktop、LangChain 等 Agent framework | 官方 MCP server:`@synapse-network/mcp-server`,设置 `SYNAPSE_AGENT_KEY=agt_xxx` | +| 把 SynapseNetwork 接入 Cursor、Claude Desktop、LangChain 等 Agent framework | 官方 MCP server:`@synapse-network-ai/mcp-server`,设置 `SYNAPSE_AGENT_KEY=agt_xxx` | | 在应用代码里直接调用服务 | 本 SDK 的 `SynapseClient` | | 签发 Agent Key 或发布 Provider API | 高级 owner/provider API:`SynapseAuth` 和 `auth.provider()` | @@ -178,12 +178,14 @@ print(receipt.invocation_id, receipt.status, receipt.charged_usdc) 步骤 2:把 key 传给 SDK。 +npm 组织是 `synapse-network-ai`;官方包统一使用 `@synapse-network-ai/*` scope。 + ```bash -npm install @synapse-network/sdk +npm install @synapse-network-ai/sdk ``` ```ts -import { SynapseClient } from "@synapse-network/sdk"; +import { SynapseClient } from "@synapse-network-ai/sdk"; const agentKey = process.env.SYNAPSE_AGENT_KEY; if (!agentKey) { @@ -265,7 +267,7 @@ TypeScript: ```ts import { Wallet } from "ethers"; -import { SynapseAuth } from "@synapse-network/sdk"; +import { SynapseAuth } from "@synapse-network-ai/sdk"; const wallet = new Wallet(process.env.OWNER_PRIVATE_KEY!); const auth = SynapseAuth.fromWallet(wallet, { environment: "staging" }); @@ -304,7 +306,7 @@ TypeScript: ```ts import { Wallet } from "ethers"; -import { SynapseAuth } from "@synapse-network/sdk"; +import { SynapseAuth } from "@synapse-network-ai/sdk"; const auth = SynapseAuth.fromWallet(new Wallet(process.env.OWNER_PRIVATE_KEY!), { environment: "staging", diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md index b8f390e..79e8344 100644 --- a/docs/guides/getting-started.md +++ b/docs/guides/getting-started.md @@ -21,7 +21,7 @@ export SYNAPSE_AGENT_KEY=agt_xxx | Goal | Use | |---|---| -| Connect an agent framework such as Cursor, Claude Desktop, or LangChain | `@synapse-network/mcp-server` | +| Connect an agent framework such as Cursor, Claude Desktop, or LangChain | `@synapse-network-ai/mcp-server` | | Write application code that invokes services directly | `SynapseClient` | | Issue agent credentials or manage provider services | `SynapseAuth` and `auth.provider()` | diff --git a/docs/ops/sdk-release-runbook.md b/docs/ops/sdk-release-runbook.md index 1a6c67c..4ce4e31 100644 --- a/docs/ops/sdk-release-runbook.md +++ b/docs/ops/sdk-release-runbook.md @@ -19,12 +19,15 @@ This runbook covers SynapseNetwork SDK package publishing. SDKs are **published* | Language | Package | Registry | Publish notes | | --- | --- | --- | --- | | Python | `synapse-client` | PyPI | Optional TestPyPI dry-run before public release. | -| TypeScript | `@synapse-network/sdk` | npm | Use npm dist-tags such as `preview`, `next`, or `latest`. | +| TypeScript | `@synapse-network-ai/sdk` | npm | Use npm dist-tags such as `preview`, `next`, or `latest`. | | Go | `github.com/SynapseNetworkAI/Synapse-Network-Sdk/go` | Go module via GitHub | Because the module is in `/go`, tags must use `go/vX.Y.Z`. | | Java | `ai.synapsenetwork:synapse-network-sdk` | Maven Central | If Central is not ready, publish preview artifacts to GitHub Packages Maven. | | .NET | `SynapseNetwork.Sdk` | NuGet.org | Use NuGet package versions and never overwrite an existing version. | | All | GitHub Release | GitHub | One release page per train with links to all language packages. | +The npm organization is `synapse-network-ai`: https://www.npmjs.com/org/synapse-network-ai. +Do not publish or document packages under the occupied legacy npm scope. + ## Preflight Run the full SDK quality gate: diff --git a/docs/sdk/README.md b/docs/sdk/README.md index 13b90a2..10b454d 100644 --- a/docs/sdk/README.md +++ b/docs/sdk/README.md @@ -73,7 +73,7 @@ bash scripts/e2e/sdk_parity_e2e.sh --env local This does not reintroduce a public local environment preset. The local target is only an explicit URL override for test automation. The script may install missing local toolchains; .NET is pinned to SDK 8.0 under `$HOME/.synapse-network-sdk-e2e/dotnet` if needed. -By default the fixed-price path only auto-selects a free fixed-price API service. If staging has no such service, set `SYNAPSE_E2E_FIXED_SERVICE_ID`, `SYNAPSE_E2E_FIXED_COST_USDC`, and `SYNAPSE_E2E_FIXED_PAYLOAD_JSON` explicitly. +By default the fixed-price path first selects the Synapse first-party smoke service `svc_synapse_echo`, then falls back to any free fixed-price API service. If staging has neither, set `SYNAPSE_E2E_FIXED_SERVICE_ID`, `SYNAPSE_E2E_FIXED_COST_USDC`, and `SYNAPSE_E2E_FIXED_PAYLOAD_JSON` explicitly. ## Staging Docs @@ -139,7 +139,7 @@ Runtime calls should include: 1. `request_id` / request header for gateway log correlation. 2. `idempotency_key` / `idempotencyKey` to avoid duplicate charges or duplicate execution. -3. For fixed-price APIs, pass `cost_usdc` / `costUsdc` from latest discovery price. If price changes, the gateway rejects the call and the caller should rediscover. +3. For fixed-price APIs, pass `cost_usdc` / `costUsdc` from latest discovery price. Smoke examples default to `svc_synapse_echo`, a free first-party echo service that returns the JSON object payload unchanged. If price changes, the gateway rejects the call and the caller should rediscover. 4. For token-metered LLM services, call `invoke_llm()` / `invokeLlm()` with optional `max_cost_usdc` / `maxCostUsdc`; final Provider `usage` drives the actual charge. ## Common Failures diff --git a/docs/sdk/README.zh-CN.md b/docs/sdk/README.zh-CN.md index 9b86ff6..7478079 100644 --- a/docs/sdk/README.zh-CN.md +++ b/docs/sdk/README.zh-CN.md @@ -73,7 +73,7 @@ bash scripts/e2e/sdk_parity_e2e.sh --env local 这不会恢复公开的 local environment preset;local 只是测试自动化里的显式 URL override。缺少本地工具链时可自动安装;.NET 会按项目 baseline 安装 SDK 8.0 到 `$HOME/.synapse-network-sdk-e2e/dotnet`。 -默认 fixed-price 路径只会自动选择免费的 fixed-price API 服务。如果 staging 没有这类服务,需要显式设置 `SYNAPSE_E2E_FIXED_SERVICE_ID`、`SYNAPSE_E2E_FIXED_COST_USDC` 和 `SYNAPSE_E2E_FIXED_PAYLOAD_JSON`。 +默认 fixed-price 路径会先选择 Synapse 第一方 smoke 服务 `svc_synapse_echo`,找不到时再 fallback 到免费的 fixed-price API 服务。如果 staging 两者都没有,需要显式设置 `SYNAPSE_E2E_FIXED_SERVICE_ID`、`SYNAPSE_E2E_FIXED_COST_USDC` 和 `SYNAPSE_E2E_FIXED_PAYLOAD_JSON`。 ## Staging 产品文档 @@ -139,7 +139,7 @@ TypeScript: 1. `request_id` / request header,用于串联 gateway 日志。 2. `idempotency_key` / `idempotencyKey`,用于避免重复扣费或重复执行。 -3. 普通 fixed-price API 传 `cost_usdc` / `costUsdc`,来自最新 discovery price。若价格变化,gateway 会拒绝本次调用,调用方应重新 discovery。 +3. 普通 fixed-price API 传 `cost_usdc` / `costUsdc`,来自最新 discovery price。Smoke examples 默认调用免费的第一方 `svc_synapse_echo`,它会原样返回 JSON object payload。若价格变化,gateway 会拒绝本次调用,调用方应重新 discovery。 4. 按 token 计费的 LLM 服务调用 `invoke_llm()` / `invokeLlm()`,可选传 `max_cost_usdc` / `maxCostUsdc`;最终按 Provider 返回的 `usage` 精准扣费。 ## 常见故障 diff --git a/docs/sdk/dotnet_integration.md b/docs/sdk/dotnet_integration.md index e022afe..79b6b46 100644 --- a/docs/sdk/dotnet_integration.md +++ b/docs/sdk/dotnet_integration.md @@ -21,13 +21,17 @@ var client = new SynapseClient(new SynapseClientOptions Environment = "staging", }); -var services = await client.SearchAsync("free", new SearchOptions { Limit = 10 }); +var services = await client.SearchAsync("svc_synapse_echo", new SearchOptions { Limit = 10 }); var service = services[0]; var price = service.Pricing?.GetProperty("amount").GetString() ?? "0"; var result = await client.InvokeAsync( service.ServiceId ?? service.Id!, - new Dictionary { ["prompt"] = "hello" }, + new Dictionary + { + ["message"] = "hello from Synapse SDK smoke", + ["metadata"] = new Dictionary { ["scenario"] = "quickstart" }, + }, new InvokeOptions { CostUsdc = price }); Console.WriteLine($"{result.InvocationId} {result.Status} {result.ChargedUsdc}"); diff --git a/docs/sdk/go_integration.md b/docs/sdk/go_integration.md index bdbc996..1fc15d2 100644 --- a/docs/sdk/go_integration.md +++ b/docs/sdk/go_integration.md @@ -32,7 +32,7 @@ func main() { panic(err) } - services, err := client.Search(context.Background(), "free", synapse.SearchOptions{Limit: 10}) + services, err := client.Search(context.Background(), "svc_synapse_echo", synapse.SearchOptions{Limit: 10}) if err != nil { panic(err) } @@ -41,7 +41,10 @@ func main() { result, err := client.Invoke( context.Background(), service.ServiceID, - map[string]any{"prompt": "hello"}, + map[string]any{ + "message": "hello from Synapse SDK smoke", + "metadata": map[string]any{"scenario": "quickstart"}, + }, synapse.InvokeOptions{CostUSDC: fmt.Sprint(service.Pricing["amount"])}, ) if err != nil { diff --git a/docs/sdk/java_integration.md b/docs/sdk/java_integration.md index cc5a7d9..07fe46e 100644 --- a/docs/sdk/java_integration.md +++ b/docs/sdk/java_integration.md @@ -23,13 +23,16 @@ import java.util.Map; SynapseClient client = new SynapseClient( SynapseClient.options(System.getenv("SYNAPSE_AGENT_KEY")).environment("staging")); -var services = client.search("free", new SynapseClient.SearchOptions()); +var services = client.search("svc_synapse_echo", new SynapseClient.SearchOptions()); var service = services.get(0); SynapseClient.InvokeOptions options = new SynapseClient.InvokeOptions(); options.costUsdc = service.pricing().path("amount").asText("0"); -var result = client.invoke(service.serviceId(), Map.of("prompt", "hello"), options); +var result = client.invoke( + service.serviceId(), + Map.of("message", "hello from Synapse SDK smoke", "metadata", Map.of("scenario", "quickstart")), + options); System.out.println(result.invocationId() + " " + result.status() + " " + result.chargedUsdc()); ``` diff --git a/docs/sdk/python_integration.md b/docs/sdk/python_integration.md index b7a92cc..c7b063d 100644 --- a/docs/sdk/python_integration.md +++ b/docs/sdk/python_integration.md @@ -174,7 +174,7 @@ print(result.synapse.charged_usdc, result.synapse.released_usdc) 示例脚本位于 `python/examples`: -1. `free_service_smoke.py`:搜索免费 fixed-price API service、invoke、读取 receipt。 +1. `free_service_smoke.py`:优先调用第一方 `svc_synapse_echo`,找不到时搜索免费 fixed-price API service、invoke、读取 receipt。 2. `llm_smoke.py`:调用 token-metered LLM,不发送 fixed-price cost。 3. `e2e.py`:完整真实 Gateway 验证并输出 JSON lines。 4. `provider_staging_onboarding.py`:使用 `SynapseAuth` + `auth.provider()` 在 staging 注册 provider service。 diff --git a/docs/sdk/typescript_integration.md b/docs/sdk/typescript_integration.md index a67b373..4e6e57f 100644 --- a/docs/sdk/typescript_integration.md +++ b/docs/sdk/typescript_integration.md @@ -89,7 +89,7 @@ Staging 接入建议先完成: ```ts import { Wallet } from "ethers"; -import { SynapseAuth } from "@synapse-network/sdk"; +import { SynapseAuth } from "@synapse-network-ai/sdk"; const wallet = new Wallet(process.env.OWNER_PRIVATE_KEY!); const auth = SynapseAuth.fromWallet(wallet, { @@ -147,7 +147,7 @@ SDK 继续兼容 `limit/offset` 入参,并映射为: 示例: ```ts -import { SynapseClient } from "@synapse-network/sdk"; +import { SynapseClient } from "@synapse-network-ai/sdk"; const client = new SynapseClient({ credential: issued.token, @@ -221,6 +221,7 @@ console.log(result.synapse?.chargedUsdc, result.synapse?.releasedUsdc); ## TypeScript Examples Runnable examples live under `typescript/examples`: +`example:free` first calls `svc_synapse_echo`, then falls back to another free fixed-price API service if echo is unavailable. ```bash cd /Users/cliff/workspace/agent/Synapse-Network-Sdk diff --git a/docs/sdk/typescript_provider_integration.md b/docs/sdk/typescript_provider_integration.md index 6a2ecb4..155e186 100644 --- a/docs/sdk/typescript_provider_integration.md +++ b/docs/sdk/typescript_provider_integration.md @@ -44,7 +44,7 @@ ```ts import { Wallet } from "ethers"; -import { SynapseAuth } from "@synapse-network/sdk"; +import { SynapseAuth } from "@synapse-network-ai/sdk"; const wallet = new Wallet(process.env.PROVIDER_PRIVATE_KEY!); diff --git a/dotnet/examples/e2e/Program.cs b/dotnet/examples/e2e/Program.cs index ccd9fce..2834014 100644 --- a/dotnet/examples/e2e/Program.cs +++ b/dotnet/examples/e2e/Program.cs @@ -2,7 +2,8 @@ using System.Text.Json; using SynapseNetwork.Sdk; -const string DefaultFixedPayload = "{\"prompt\":\"hello\"}"; +const string SynapseEchoServiceId = "svc_synapse_echo"; +const string DefaultFixedPayload = "{\"message\":\"hello from Synapse SDK smoke\",\"metadata\":{\"scenario\":\"fixed-price\"}}"; const string DefaultLlmPayload = "{\"messages\":[{\"role\":\"user\",\"content\":\"hello\"}]}"; var credential = RequireEnv("SYNAPSE_AGENT_KEY"); @@ -110,14 +111,22 @@ static async Task FixedTarget(SynapseClient client, Cancella return new FixedServiceTarget(configuredServiceId.Trim(), cost.Trim(), payload); } + var echoServices = await client.SearchAsync(SynapseEchoServiceId, new SearchOptions { Limit = 10 }, cancellationToken); + foreach (var service in echoServices) + { + var amount = PricingAmount(service); + if (string.Equals(service.ServiceId, SynapseEchoServiceId, StringComparison.Ordinal) + && IsFreeFixedApiService(service, amount)) + { + return new FixedServiceTarget(service.ServiceId, amount, payload); + } + } + var services = await client.SearchAsync("free", new SearchOptions { Limit = 25 }, cancellationToken); foreach (var service in services) { var amount = PricingAmount(service); - if (!string.IsNullOrWhiteSpace(service.ServiceId) - && string.Equals(service.ServiceKind, "api", StringComparison.OrdinalIgnoreCase) - && string.Equals(service.PriceModel, "fixed", StringComparison.OrdinalIgnoreCase) - && DecimalEquals(amount, "0")) + if (IsFreeFixedApiService(service, amount)) { return new FixedServiceTarget(service.ServiceId, amount, payload); } @@ -126,6 +135,14 @@ static async Task FixedTarget(SynapseClient client, Cancella throw new InvalidOperationException("unreachable"); } +static bool IsFreeFixedApiService(ServiceRecord service, string amount) +{ + return !string.IsNullOrWhiteSpace(service.ServiceId) + && string.Equals(service.ServiceKind, "api", StringComparison.OrdinalIgnoreCase) + && string.Equals(service.PriceModel, "fixed", StringComparison.OrdinalIgnoreCase) + && DecimalEquals(amount, "0"); +} + static async Task AwaitReceipt(SynapseClient client, string? invocationId, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(invocationId)) diff --git a/dotnet/examples/free-service-smoke/Program.cs b/dotnet/examples/free-service-smoke/Program.cs index 4ab3255..a29089a 100644 --- a/dotnet/examples/free-service-smoke/Program.cs +++ b/dotnet/examples/free-service-smoke/Program.cs @@ -11,20 +11,53 @@ Credential = agentKey, Environment = "staging", }); -var services = await client.SearchAsync("free", new SearchOptions { Limit = 10 }); -if (services.Count == 0) +var service = await FindFixedSmokeService(client); +var price = PricingAmount(service); +var result = await client.InvokeAsync( + service.ServiceId ?? service.Id ?? throw new InvalidOperationException("service id missing"), + new Dictionary + { + ["message"] = "hello from Synapse SDK smoke", + ["metadata"] = new Dictionary { ["scenario"] = "free-service-smoke" }, + }, + new InvokeOptions { CostUsdc = price }); +Console.WriteLine($"{result.InvocationId} {result.Status} {result.ChargedUsdc}"); + +static async Task FindFixedSmokeService(SynapseClient client) { - throw new InvalidOperationException("no services found"); + const string synapseEchoServiceId = "svc_synapse_echo"; + var echoServices = await client.SearchAsync(synapseEchoServiceId, new SearchOptions { Limit = 10 }); + foreach (var service in echoServices) + { + if (service.ServiceId == synapseEchoServiceId && IsFreeFixedApiService(service)) + { + return service; + } + } + + var services = await client.SearchAsync("free", new SearchOptions { Limit = 10 }); + foreach (var service in services) + { + if (IsFreeFixedApiService(service)) + { + return service; + } + } + throw new InvalidOperationException("no free fixed-price API service found; set SYNAPSE_E2E_FIXED_SERVICE_ID and SYNAPSE_E2E_FIXED_COST_USDC for paid smoke tests"); } -var service = services[0]; -var price = "0"; -if (service.Pricing.HasValue && service.Pricing.Value.TryGetProperty("amount", out var amount)) +static string PricingAmount(ServiceRecord service) { - price = amount.GetString() ?? "0"; + return service.Pricing.HasValue && service.Pricing.Value.TryGetProperty("amount", out var amount) + ? amount.GetString() ?? "" + : ""; +} + +static bool IsFreeFixedApiService(ServiceRecord service) +{ + return !string.IsNullOrWhiteSpace(service.ServiceId) + && string.Equals(service.ServiceKind, "api", StringComparison.OrdinalIgnoreCase) + && string.Equals(service.PriceModel, "fixed", StringComparison.OrdinalIgnoreCase) + && decimal.TryParse(PricingAmount(service), out var amount) + && amount == 0; } -var result = await client.InvokeAsync( - service.ServiceId ?? service.Id ?? throw new InvalidOperationException("service id missing"), - new Dictionary { ["prompt"] = "hello" }, - new InvokeOptions { CostUsdc = price }); -Console.WriteLine($"{result.InvocationId} {result.Status} {result.ChargedUsdc}"); diff --git a/go/examples/e2e/main.go b/go/examples/e2e/main.go index 6dba439..5f97ed1 100644 --- a/go/examples/e2e/main.go +++ b/go/examples/e2e/main.go @@ -14,7 +14,8 @@ import ( ) const ( - defaultFixedPayload = `{"prompt":"hello"}` + synapseEchoServiceID = "svc_synapse_echo" + defaultFixedPayload = `{"message":"hello from Synapse SDK smoke","metadata":{"scenario":"fixed-price"}}` defaultLLMPayload = `{"messages":[{"role":"user","content":"hello"}]}` ) @@ -138,14 +139,20 @@ func fixedService(ctx context.Context, client *synapse.Client) (string, string, return serviceID, cost, payload } - services, err := client.Search(ctx, "free", synapse.SearchOptions{Limit: 25}) + services, err := client.Search(ctx, synapseEchoServiceID, synapse.SearchOptions{Limit: 10}) must(err) for _, service := range services { amount := moneyString(service.Pricing["amount"]) - if strings.TrimSpace(service.ServiceID) != "" && - strings.EqualFold(service.ServiceKind, "api") && - strings.EqualFold(service.PriceModel, "fixed") && - decimalEqual(amount, "0") { + if service.ServiceID == synapseEchoServiceID && isFreeFixedAPIService(service, amount) { + return service.ServiceID, amount, payload + } + } + + services, err = client.Search(ctx, "free", synapse.SearchOptions{Limit: 25}) + must(err) + for _, service := range services { + amount := moneyString(service.Pricing["amount"]) + if isFreeFixedAPIService(service, amount) { return service.ServiceID, amount, payload } } @@ -153,6 +160,13 @@ func fixedService(ctx context.Context, client *synapse.Client) (string, string, return "", "", nil } +func isFreeFixedAPIService(service synapse.ServiceRecord, amount string) bool { + return strings.TrimSpace(service.ServiceID) != "" && + strings.EqualFold(service.ServiceKind, "api") && + strings.EqualFold(service.PriceModel, "fixed") && + decimalEqual(amount, "0") +} + func awaitReceipt(ctx context.Context, client *synapse.Client, invocationID string) *synapse.InvocationResult { if strings.TrimSpace(invocationID) == "" { fail("invoke returned empty invocationId") diff --git a/go/examples/free_service_smoke/main.go b/go/examples/free_service_smoke/main.go index c293ed4..de92845 100644 --- a/go/examples/free_service_smoke/main.go +++ b/go/examples/free_service_smoke/main.go @@ -3,11 +3,15 @@ package main import ( "context" "fmt" + "math/big" "os" + "strings" synapse "github.com/SynapseNetworkAI/Synapse-Network-Sdk/go/synapse" ) +const synapseEchoServiceID = "svc_synapse_echo" + func main() { agentKey := os.Getenv("SYNAPSE_AGENT_KEY") if agentKey == "" { @@ -17,21 +21,54 @@ func main() { if err != nil { panic(err) } - services, err := client.Search(context.Background(), "free", synapse.SearchOptions{Limit: 10}) + ctx := context.Background() + service := findFixedSmokeService(ctx, client) + result, err := client.Invoke( + ctx, + service.ServiceID, + map[string]any{ + "message": "hello from Synapse SDK smoke", + "metadata": map[string]any{"scenario": "free-service-smoke"}, + }, + synapse.InvokeOptions{CostUSDC: fmt.Sprint(service.Pricing["amount"])}, + ) if err != nil { panic(err) } - if len(services) == 0 { - panic("no services found") + fmt.Println(result.InvocationID, result.Status, result.ChargedUSDC) +} + +func findFixedSmokeService(ctx context.Context, client *synapse.Client) synapse.ServiceRecord { + services, err := client.Search(ctx, synapseEchoServiceID, synapse.SearchOptions{Limit: 10}) + if err != nil { + panic(err) } - result, err := client.Invoke( - context.Background(), - services[0].ServiceID, - map[string]any{"prompt": "hello"}, - synapse.InvokeOptions{CostUSDC: fmt.Sprint(services[0].Pricing["amount"])}, - ) + for _, service := range services { + if service.ServiceID == synapseEchoServiceID && isFreeFixedAPIService(service) { + return service + } + } + + services, err = client.Search(ctx, "free", synapse.SearchOptions{Limit: 10}) if err != nil { panic(err) } - fmt.Println(result.InvocationID, result.Status, result.ChargedUSDC) + for _, service := range services { + if isFreeFixedAPIService(service) { + return service + } + } + panic("no free fixed-price API service found; set SYNAPSE_E2E_FIXED_SERVICE_ID and SYNAPSE_E2E_FIXED_COST_USDC for paid smoke tests") +} + +func isFreeFixedAPIService(service synapse.ServiceRecord) bool { + amount := fmt.Sprint(service.Pricing["amount"]) + left, ok := new(big.Rat).SetString(amount) + if !ok { + return false + } + return strings.TrimSpace(service.ServiceID) != "" && + strings.EqualFold(service.ServiceKind, "api") && + strings.EqualFold(service.PriceModel, "fixed") && + left.Sign() == 0 } diff --git a/java/examples/src/main/java/ai/synapsenetwork/sdk/examples/ExampleSupport.java b/java/examples/src/main/java/ai/synapsenetwork/sdk/examples/ExampleSupport.java index 99cbf4c..92cf8d9 100644 --- a/java/examples/src/main/java/ai/synapsenetwork/sdk/examples/ExampleSupport.java +++ b/java/examples/src/main/java/ai/synapsenetwork/sdk/examples/ExampleSupport.java @@ -10,6 +10,13 @@ import java.util.Map; final class ExampleSupport { + static final String SYNAPSE_ECHO_SERVICE_ID = "svc_synapse_echo"; + static final Map DEFAULT_FIXED_PAYLOAD = + Map.of( + "message", + "hello from Synapse SDK smoke", + "metadata", + Map.of("scenario", "fixed-price")); private static final ObjectMapper MAPPER = new ObjectMapper(); private ExampleSupport() {} @@ -30,7 +37,7 @@ static SynapseClient client(String credential) { } static FixedTarget fixedTarget(SynapseClient client) { - Map configuredPayload = payload("SYNAPSE_E2E_FIXED_PAYLOAD_JSON", Map.of("prompt", "hello")); + Map configuredPayload = payload("SYNAPSE_E2E_FIXED_PAYLOAD_JSON", DEFAULT_FIXED_PAYLOAD); String configuredServiceId = System.getenv("SYNAPSE_E2E_FIXED_SERVICE_ID"); if (configuredServiceId != null && !configuredServiceId.isBlank()) { String cost = System.getenv("SYNAPSE_E2E_FIXED_COST_USDC"); @@ -41,8 +48,17 @@ static FixedTarget fixedTarget(SynapseClient client) { } SynapseClient.SearchOptions options = new SynapseClient.SearchOptions(); + options.limit = 10; + List services = client.search(SYNAPSE_ECHO_SERVICE_ID, options); + for (SynapseClient.ServiceRecord service : services) { + String amount = pricingAmount(service); + if (SYNAPSE_ECHO_SERVICE_ID.equals(service.serviceId()) && isFreeFixedApiService(service)) { + return new FixedTarget(service.serviceId(), amount, configuredPayload); + } + } + options.limit = 25; - List services = client.search("free", options); + services = client.search("free", options); for (SynapseClient.ServiceRecord service : services) { String amount = pricingAmount(service); if (isFreeFixedApiService(service)) { diff --git a/java/examples/src/main/java/ai/synapsenetwork/sdk/examples/FreeServiceSmoke.java b/java/examples/src/main/java/ai/synapsenetwork/sdk/examples/FreeServiceSmoke.java index 49d7c15..6a31eff 100644 --- a/java/examples/src/main/java/ai/synapsenetwork/sdk/examples/FreeServiceSmoke.java +++ b/java/examples/src/main/java/ai/synapsenetwork/sdk/examples/FreeServiceSmoke.java @@ -1,27 +1,17 @@ package ai.synapsenetwork.sdk.examples; import ai.synapsenetwork.sdk.SynapseClient; -import java.util.Map; public final class FreeServiceSmoke { private FreeServiceSmoke() {} public static void main(String[] args) { SynapseClient client = ExampleSupport.client(); - var services = client.search("free", new SynapseClient.SearchOptions()); - var service = - services.stream() - .filter(ExampleSupport::isFreeFixedApiService) - .findFirst() - .orElseThrow( - () -> - new IllegalStateException( - "no free fixed-price API service found; set SYNAPSE_E2E_FIXED_SERVICE_ID and " - + "SYNAPSE_E2E_FIXED_COST_USDC for paid smoke tests")); + var target = ExampleSupport.fixedTarget(client); SynapseClient.InvokeOptions options = new SynapseClient.InvokeOptions(); - options.costUsdc = service.pricing().path("amount").asText("0"); + options.costUsdc = target.costUsdc(); options.idempotencyKey = "java-free-smoke-" + System.currentTimeMillis(); - var result = client.invoke(service.serviceId(), Map.of("prompt", "hello"), options); + var result = client.invoke(target.serviceId(), target.payload(), options); var receipt = ExampleSupport.awaitReceipt(client, result.invocationId()); ExampleSupport.emit( "free-service-smoke", @@ -29,6 +19,6 @@ public static void main(String[] args) { result.invocationId(), receipt.chargedUsdc(), receipt.status(), - service.serviceId()); + target.serviceId()); } } diff --git a/llm-instructions.md b/llm-instructions.md index d8e89fa..f37d82a 100644 --- a/llm-instructions.md +++ b/llm-instructions.md @@ -74,7 +74,7 @@ TypeScript pattern: TypeScript 模式: ```ts -import { SynapseClient } from "@synapse-network/sdk"; +import { SynapseClient } from "@synapse-network-ai/sdk"; const agentKey = process.env.SYNAPSE_AGENT_KEY; if (!agentKey) throw new Error("SYNAPSE_AGENT_KEY is required"); @@ -130,7 +130,7 @@ service = provider.register_service( ``` ```ts -import { SynapseAuth } from "@synapse-network/sdk"; +import { SynapseAuth } from "@synapse-network-ai/sdk"; const auth = SynapseAuth.fromWallet(wallet, { environment: "staging" }); const provider = auth.provider(); diff --git a/llms.txt b/llms.txt index c88d5c9..4b032ca 100644 --- a/llms.txt +++ b/llms.txt @@ -28,8 +28,9 @@ Do not generate private local gateway setup instructions for public SDK users. If you are integrating SynapseNetwork into an agent framework such as Cursor, Claude Desktop, or LangChain, prefer the official MCP server instead of writing direct SDK code. -- Registry: `@synapse-network/mcp-server` +- Registry: `@synapse-network-ai/mcp-server` - Environment variable: `SYNAPSE_AGENT_KEY=agt_xxx` +- npm organization: `synapse-network-ai`; use the `@synapse-network-ai/*` scope for official npm packages. ## SDK Quickstart: Consumer Agent @@ -47,7 +48,7 @@ Use exactly one invocation mode: ### TypeScript ```typescript -import { SynapseClient } from "@synapse-network/sdk"; +import { SynapseClient } from "@synapse-network-ai/sdk"; const agentKey = process.env.SYNAPSE_AGENT_KEY; if (!agentKey) { diff --git a/python/examples/e2e.py b/python/examples/e2e.py index 5a4f07d..85e5a9d 100644 --- a/python/examples/e2e.py +++ b/python/examples/e2e.py @@ -10,7 +10,11 @@ from synapse_client import AuthenticationError, InvokeError, SynapseClient -DEFAULT_FIXED_PAYLOAD = {"prompt": "hello"} +SYNAPSE_ECHO_SERVICE_ID = "svc_synapse_echo" +DEFAULT_FIXED_PAYLOAD = { + "message": "hello from Synapse SDK smoke", + "metadata": {"scenario": "fixed-price"}, +} DEFAULT_LLM_PAYLOAD = {"messages": [{"role": "user", "content": "hello"}]} @@ -106,6 +110,17 @@ def fixed_target(client: SynapseClient) -> tuple[str, str, dict[str, Any]]: fail("SYNAPSE_E2E_FIXED_COST_USDC is required when SYNAPSE_E2E_FIXED_SERVICE_ID is set") return configured_service_id, configured_cost, payload + services = client.search(SYNAPSE_ECHO_SERVICE_ID, limit=10) + for service in services: + amount = str(service.pricing.amount) + if ( + service.service_id == SYNAPSE_ECHO_SERVICE_ID + and service.service_kind.lower() == "api" + and service.price_model.lower() == "fixed" + and Decimal(amount) == Decimal("0") + ): + return service.service_id, amount, payload + services = client.search("free", limit=25) for service in services: amount = str(service.pricing.amount) diff --git a/python/examples/free_service_smoke.py b/python/examples/free_service_smoke.py index 56de811..16b17cf 100644 --- a/python/examples/free_service_smoke.py +++ b/python/examples/free_service_smoke.py @@ -7,23 +7,43 @@ from synapse_client import SynapseClient +SYNAPSE_ECHO_SERVICE_ID = "svc_synapse_echo" +DEFAULT_FIXED_PAYLOAD = { + "message": "hello from Synapse SDK smoke", + "metadata": {"scenario": "free-service-smoke"}, +} + + def main() -> None: client = SynapseClient( api_key=require_env("SYNAPSE_AGENT_KEY"), gateway_url=os.getenv("SYNAPSE_GATEWAY_URL") or None, environment="staging", ) - services = client.search("free", limit=25) + services = client.search(SYNAPSE_ECHO_SERVICE_ID, limit=10) service = next( ( candidate for candidate in services - if candidate.service_kind.lower() == "api" + if candidate.service_id == SYNAPSE_ECHO_SERVICE_ID + and candidate.service_kind.lower() == "api" and candidate.price_model.lower() == "fixed" - and str(candidate.pricing.amount) == "0" + and str(candidate.pricing.amount) in {"0", "0.000000"} ), None, ) + if service is None: + services = client.search("free", limit=25) + service = next( + ( + candidate + for candidate in services + if candidate.service_kind.lower() == "api" + and candidate.price_model.lower() == "fixed" + and str(candidate.pricing.amount) in {"0", "0.000000"} + ), + None, + ) if service is None: raise RuntimeError( "no free fixed-price API service found; set SYNAPSE_E2E_FIXED_SERVICE_ID " @@ -32,7 +52,7 @@ def main() -> None: result = client.invoke( service.service_id, - {"prompt": "hello"}, + DEFAULT_FIXED_PAYLOAD, cost_usdc=str(service.pricing.amount), idempotency_key=f"python-free-smoke-{uuid4().hex}", ) diff --git a/scripts/ci/repo_hygiene_checks.sh b/scripts/ci/repo_hygiene_checks.sh index 881ae1e..37aac9b 100755 --- a/scripts/ci/repo_hygiene_checks.sh +++ b/scripts/ci/repo_hygiene_checks.sh @@ -16,6 +16,14 @@ echo "[ci:hygiene] checking public preview staging defaults" grep -RIn --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=.venv \ "https://api-staging.synapse-network.ai" README.md docs python/synapse_client typescript/src typescript/tests >/dev/null +echo "[ci:hygiene] checking npm package scope" +OLD_NPM_SCOPE="@synapse-network""/" +if grep -RInF --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=.venv --exclude-dir=dist --exclude-dir=build --exclude-dir=coverage --exclude-dir=.pytest_cache \ + "$OLD_NPM_SCOPE" README.md README.zh-CN.md docs llms.txt llm-instructions.md typescript/package.json typescript/package-lock.json typescript/examples typescript/src typescript/tests; then + echo "[ci:hygiene] old npm scope detected; use @synapse-network-ai/sdk or @synapse-network-ai/mcp-server" >&2 + exit 1 +fi + echo "[ci:hygiene] checking removed local gateway guidance" if grep -RInE --exclude-dir=.git --exclude-dir=node_modules --exclude-dir=.venv --exclude-dir=dist --exclude-dir=build --exclude-dir=coverage --exclude-dir=.pytest_cache \ '(127\.0\.0\.1:8000|localhost:8000|scripts/local|Hardhat)' README.md README.zh-CN.md docs python/synapse_client/test typescript/tests; then diff --git a/scripts/e2e/sdk_local_evidence.sh b/scripts/e2e/sdk_local_evidence.sh index 61a91c7..5a47233 100755 --- a/scripts/e2e/sdk_local_evidence.sh +++ b/scripts/e2e/sdk_local_evidence.sh @@ -48,7 +48,7 @@ export E2E_RUN_ID export E2E_OUT_DIR="$OUT_DIR" export SYNAPSE_GATEWAY_URL="${SYNAPSE_GATEWAY_URL:-http://127.0.0.1:8000}" export SYNAPSE_OWNER_PRIVATE_KEY="${SYNAPSE_OWNER_PRIVATE_KEY:-$PRIVATE_KEY}" -export SYNAPSE_E2E_FIXED_SERVICE_ID="${SYNAPSE_E2E_FIXED_SERVICE_ID:-svc_oss_security_healthcheck}" +export SYNAPSE_E2E_FIXED_SERVICE_ID="${SYNAPSE_E2E_FIXED_SERVICE_ID:-svc_synapse_echo}" export SYNAPSE_E2E_FIXED_COST_USDC="${SYNAPSE_E2E_FIXED_COST_USDC:-0.000000}" export SYNAPSE_E2E_LLM_SERVICE_ID="${SYNAPSE_E2E_LLM_SERVICE_ID:-svc_deepseek_chat}" export SYNAPSE_E2E_LLM_MAX_COST_USDC="${SYNAPSE_E2E_LLM_MAX_COST_USDC:-0.010000}" @@ -77,7 +77,7 @@ fi export POSTGRES_READONLY_DSN if [[ -z "${SYNAPSE_E2E_FIXED_PAYLOAD_JSON:-}" ]]; then - export SYNAPSE_E2E_FIXED_PAYLOAD_JSON='{"packageList":["requests==2.31.0"]}' + export SYNAPSE_E2E_FIXED_PAYLOAD_JSON='{"message":"hello from Synapse SDK smoke","metadata":{"scenario":"fixed-price"}}' fi if [[ -z "${SYNAPSE_E2E_LLM_PAYLOAD_JSON:-}" ]]; then export SYNAPSE_E2E_LLM_PAYLOAD_JSON='{"messages":[{"role":"user","content":"hello from sdk local e2e"}]}' diff --git a/scripts/e2e/sdk_wave1_local.sh b/scripts/e2e/sdk_wave1_local.sh index 5145910..ad5f5a9 100755 --- a/scripts/e2e/sdk_wave1_local.sh +++ b/scripts/e2e/sdk_wave1_local.sh @@ -26,7 +26,7 @@ Required: Optional: SYNAPSE_GATEWAY_URL Explicit Gateway URL; defaults to SDK staging - SYNAPSE_E2E_FIXED_SERVICE_ID Fixed-price API service override + SYNAPSE_E2E_FIXED_SERVICE_ID Fixed-price API service override; defaults to svc_synapse_echo when present SYNAPSE_E2E_FIXED_COST_USDC Required with SYNAPSE_E2E_FIXED_SERVICE_ID SYNAPSE_E2E_FIXED_PAYLOAD_JSON SYNAPSE_E2E_LLM_SERVICE_ID Default: svc_deepseek_chat diff --git a/typescript/examples/_shared.ts b/typescript/examples/_shared.ts index dc92e58..48c9864 100644 --- a/typescript/examples/_shared.ts +++ b/typescript/examples/_shared.ts @@ -1,7 +1,11 @@ import { AuthenticationError, InvokeError, SynapseClient } from "../src"; import type { ServiceRecord } from "../src"; -export const DEFAULT_FIXED_PAYLOAD: Record = { prompt: "hello" }; +export const SYNAPSE_ECHO_SERVICE_ID = "svc_synapse_echo"; +export const DEFAULT_FIXED_PAYLOAD: Record = { + message: "hello from Synapse SDK smoke", + metadata: { scenario: "fixed-price" }, +}; export const DEFAULT_LLM_PAYLOAD: Record = { messages: [{ role: "user", content: "hello" }], }; @@ -73,6 +77,18 @@ export async function fixedTarget( return { serviceId: configuredServiceId, costUsdc: configuredCost, payload }; } + const echoServices = await synapse.search(SYNAPSE_ECHO_SERVICE_ID, { limit: 10 }); + const echoService = echoServices.find( + (service) => (service.serviceId ?? service.id) === SYNAPSE_ECHO_SERVICE_ID && isFreeFixedApiService(service) + ); + if (echoService) { + return { + serviceId: echoService.serviceId ?? echoService.id ?? "", + costUsdc: pricingAmount(echoService), + payload, + }; + } + const services = await synapse.search("free", { limit: 25 }); const service = services.find(isFreeFixedApiService); if (!service) { diff --git a/typescript/package-lock.json b/typescript/package-lock.json index 0fbdef2..03752d2 100644 --- a/typescript/package-lock.json +++ b/typescript/package-lock.json @@ -1,11 +1,11 @@ { - "name": "@synapse-network/sdk", + "name": "@synapse-network-ai/sdk", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "@synapse-network/sdk", + "name": "@synapse-network-ai/sdk", "version": "0.1.0", "dependencies": { "uuid": "^9.0.0" diff --git a/typescript/package.json b/typescript/package.json index da50b2d..b8f831a 100644 --- a/typescript/package.json +++ b/typescript/package.json @@ -1,9 +1,12 @@ { - "name": "@synapse-network/sdk", + "name": "@synapse-network-ai/sdk", "version": "0.1.0", "description": "Official TypeScript SDK for the Synapse Network API Gateway", "main": "dist/index.js", "types": "dist/index.d.ts", + "publishConfig": { + "access": "public" + }, "scripts": { "build": "tsc", "test": "jest --testPathPattern='tests/unit' --runInBand --forceExit",