Repository navigation
[2/7] feat(core-api): talk to the swanlab server over http - #1806
Merged
Merged
Conversation
Keep `core/.smoke-tmp/` out of git: the playground smoke runner copies `smoke/go/sN` there and runs it inside the module so it can import `core/internal/**` without shipping temporary packages.
Add api_key (11), api_host (12) and web_host (13) so the Go core can authenticate against the cloud with the same field numbers as the ApiKey auth refactor, and regenerate both the Go and Python stubs. api_key is a secret: it is only transported over DeliverRunStart, never via command line arguments, and must be masked in any log output.
Keep business endpoints out of this package: it exposes a single
`Do(Request) (*Response, error)` round trip and owns everything about
how a request is sent.
- shared *http.Transport pool, per (api_host, sha256(api_key)) Client
reuse, 60 s timeout, 5 retries, 0.5 backoff on 429/5xx and the
X-SwanLab-SDK-Version header
- auth: stateless `Authorization: ApiKey` with a one-shot
/api/auth/verify probe; legacy deployments fall back to sid cookies
with a 7-day refresh window and locked relogin on 401
- APIError{Retryable, Code, Message, TraceID} with IsRetryable /
IsAuthenticationError / IsInvalidResponse / IsHouseRejected, plus
Response.HouseRejected for "202 instead of 200" writes
- clienttest: shared httptest fake cloud and injectable clock, reused by
the client and api test suites
Layer the REST protocol on top of internal/client so transport policy and protocol semantics can evolve separately: `api` depends on `client` and never the other way around. - model.go: every request/response body and protocol enum in one place (Experiment, Project, Summary, ColumnUpload, PrepareSaveFile, MultipartFile, ResumeMode, RunState, MetricKind, ...), no more endpoint-local anonymous structs - api.go: project / experiment / metrics / series / presigned / save / profile / heartbeat - result rules: House accepts only 200 (202 is a non-retryable rejection), experiment create distinguishes 200/201, createdAt and finishedAt are taken at request time, prepare responses are checked for element count and empty entries
…deflake the cancel test - raise MaxIdleConnsPerHost from 2 to 16 (64 overall) on the cloned transport: the upload pipeline drives several tracks and file uploads concurrently, and the default value keeps dropping keep-alives - move Factory.Client's auth probe outside the mutex so one slow identity cannot block other session startups; concurrent callers for the same identity still converge on the first cached instance - drive TestContextCancellationIsNotRetried from a handler signal instead of a 20 ms sleep, which races with request dispatch on loaded CI runners - cover both changes with pool-parameter and concurrent-factory tests
…rule Both notes exist to stop a future reader from "fixing" the current behaviour by accident: - empty batches are the sender's responsibility: Python short-circuits in its api layer, but Go takes `metrics any`, so empty-checking here would need reflection; the pipeline knows the concrete slice - House 202 is rejected on purpose even though Python's `response.ok` accepts it, so silently dropped writes surface as errors
…nections - forward CloseIdleConnections through retryTransport: Factory.Close was a no-op because the wrapper never exposed the method, so idle connections were never released - drop the 401 relogin and replay path: Python treats 401 as a plain auth error, and the loop could trigger one login per concurrent stale request - classify auth response read failures as transport errors instead of protocol errors, so a truncated body keeps its retry semantics - normalize api_host via parsed.Host so IPv6 literals keep their brackets
Validate presigned and Save prepare responses up front instead of letting empty URLs, a missing uploadId or a broken part list reach the upload stage, where they would surface as retryable network errors: - urls must match the request size and every entry must be non-empty - uploadId must be non-empty - part count must match the requested count, part numbers must be 1..N with a URL each
Nexisato
marked this pull request as ready for review
October 28, 2026 09:10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概要
新增 SwanLab 云端通信层:REST 语义、鉴权与重试对齐 Python;House 写入严格只认 200(比 Python 更严格)。
变更内容
拆成两层,依赖方向固定为
api → client:internal/clientclient.go*http.Transport、按(api_host, sha256(api_key))复用 Client、Do(Request) (*Response, error)原语auth.goAuthorization: ApiKey+/api/auth/verify探活;旧后端退化为 sid Cookie(提前 7 天主动换发;401 直接按鉴权错误返回,不在请求层重放)retry.goRetry-After、按请求关闭重试errors.goAPIError{Retryable, Code, Message, TraceID}与错误分类internal/apiapi.gomodel.gointernal/clienttestclienttest.go关键行为:
/house/metrics与心跳仅以200为成功,202视为不可重试失败,并保留后端原文与 traceid。Python 当前把202当成功,此处有意收紧,让被静默丢弃的写入显式失败。201(新建)与200(已存在);resume="must"时实验不存在直接报错。createdAt / finishedAt取发起请求时的 UTC 时间。uploadId非空、分片数匹配且编号连续为1..N),不符合契约直接判为不可重试协议错误,不进入上传阶段。Factory.Close释放空闲连接;鉴权探活不占用全局锁,单身份慢探活不阻塞其它会话启动。同时为
CoreSettings增加api_key/api_host/web_host(11/12/13),并重新生成 Go / Python 代码。验证
go build ./...(linux / darwin / windows)、go vet ./...、go test -race ./...通过;internal/client/internal/api在-count=10 -cpu=1下重复运行无偶发失败。golangci-lint run0 issues;make proto无漂移。127.0.0.1):6 个场景 61 项断言通过。Base:
feat/core/io-model。