Genspark 账单导入器(Super Agent — 仅对账)
spendguard-importer-genspark crate(D16)是一个 Rust 二进制 +
库,它会:
- 从 Genspark 管理端 API
(
/v1/admin/usage?workspace=…&from=…&to=…)拉取用量 —— 用livefeature gate 起来,所以默认 build 不带 HTTP。 - 通过内置价格表把 credits 换算成预估 USD,价格表在
services/importer_genspark/assets/genspark_credit_prices.json。 - 产出带
reservation_source=subscription_meter和import_source=genspark_team_api标签的签名spendguard.audit.import.genspark_creditCloudEvents —— 它们和 D13 subscription-meter 行、D14 Devin 导入行落在同一块 dashboard 面上。
默认的 merge gate 是消毒过的 fixture 回放 —— 已提交的
tests/fixtures/genspark_usage.json 快照。Live 模式需要 operator
显式 opt-in。
Credit → $ 换算
Section titled “Credit → $ 换算”| Plan | 月费 $ | 月度 credit 额度 | usd_per_credit |
|------|-----------|---------------------|-------------------|
| plus | $19.99 | 10,000 | $0.001999/credit |
| pro | $24.99 | 12,500 | $0.0019992/credit |
| premium | $249.99 | 125,000 | $0.00199992/credit |
换算是纯函数:以 micro-USD 为单位取
round(credits_consumed × usd_per_credit × 1_000_000),溢出时在
i64::MAX 处饱和。换算那一刻价格表的
pricing_version 会盖在每一条产出行上 —— 事后回改费率文件不会重写
历史审计行。
未知 plan 的兜底
Section titled “未知 plan 的兜底”如果 Genspark API 返回的 plan slug 不在内置价格表里(比如未来某个
"enterprise" 档,或窗口中途改了名),导入器会把这一行以
amount_micro_usd = 0 产出,并盖上
reason_code = "genspark_plan_unknown"。Dashboard 必须把
**“费率未知”和”零花费”**区分开 —— credit 字段照样填满,所以即便
美元映射未知,operator 也能看到 Genspark 的消耗量。这两个字段总是一起
设置;只设其一,要么会悄悄定错价(看得见非零 USD 却没有费率),要么让
dashboard 拿不到任何信号。
Fixture 模式(默认 merge gate)
Section titled “Fixture 模式(默认 merge gate)”cargo run -p spendguard-importer-genspark --bin importer_genspark -- \ --mode fixture \ --fixture services/importer_genspark/tests/fixtures/genspark_usage.json \ --tenant <your-tenant> \ --budget <your-budget-id>二进制会为每条记录打一个 CloudEvent 到 stdout;demo runner 脚本
(deploy/demo/import_genspark_fixture_demo.sh)把 INSERT
路径接到 demo postgres 上。
Live 模式(operator opt-in)
Section titled “Live 模式(operator opt-in)”export GENSPARK_API_TOKEN=<Admin API token, at least 32 chars>export GENSPARK_API_BASE_URL=https://api.genspark.ai/v1 # optional override
cargo run -p spendguard-importer-genspark --features live --bin importer_genspark -- \ --mode live \ --tenant <your-tenant> \ --budget <your-budget-id>live feature 只拉带 rustls-tls 的 reqwest(不带
native-tls,不带 openssl-sys),用 30s 的 per-request 超时
外加有上限的指数退避(cap:1 小时)来扛住限流。
针对 GENSPARK_API_TOKEN 的运行时 gate 在启动时做三项检查,各自报不同的
错误信息,方便 operator 排查:
- Missing —— 环境变量没设。
- Empty —— 环境变量设了,但 trim 掉空白后是空的。
- Too short —— 值不足 32 个字符(用来挡
"TODO"/"changeme"这类占位符)。
把 token 的权限只收到 Genspark Admin Usage API 这一个 scope —— 最小权限。
重跑同一个窗口不会重复产出。CloudEvent 的
event.id 是由
(workspace_id, task_id, window_end) 推导出的确定性 UUIDv5;canonical_ingest
通过已有的 event_replay_dedup 表去重。
demo 你想重跑多少遍都行:
make -C deploy/demo demo-verify-import-genspark-fixturemake -C deploy/demo demo-verify-import-genspark-fixture # idempotent; 0 new rowsCloudEvent 示例
Section titled “CloudEvent 示例”{ "specversion": "1.0", "type": "spendguard.audit.import.genspark_credit", "source": "spendguard-importer-genspark", "id": "018f4a3a-d971-7c16-91fe-d0169e715ba0", "time": "2026-06-08T12:00:00Z", "datacontenttype": "application/json", "subject": "tenant/demo/genspark/workspace/FAKE_ws_001/task/FAKE_task_001", "data": { "schema_version": "v1alpha1", "tenant_id": "demo", "budget_id": "genspark-budget", "workspace_id": "FAKE_ws_001", "task_id": "FAKE_task_001", "plan": "plus", "credits_consumed": 3200.0, "usd_per_credit": 0.001999, "amount_micro_usd": 6396800, "reason_code": null, "pricing_version": "genspark-credit-v1-2026-06", "window_start": "2026-06-01T00:00:00Z", "window_end": "2026-06-01T01:00:00Z", "reservation_source": "subscription_meter", "import_source": "genspark_team_api", "ingestion_mode": "fixture", "fixture_provenance_sha256": "fd2c0bb772bfbf2605ce09204aed0025cd754c3edce7296ee281637e5a52baf6", "task_category": "research" }}{ "pricing_version": "genspark-credit-v1-2026-06", "effective_from": "2026-06-01T00:00:00Z", "currency": "USD", "rates": [ { "plan": "plus", "monthly_usd": 19.99, "monthly_credits": 10000, "usd_per_credit": 0.001999 }, { "plan": "pro", "monthly_usd": 24.99, "monthly_credits": 12500, "usd_per_credit": 0.0019992 }, { "plan": "premium", "monthly_usd": 249.99, "monthly_credits": 125000, "usd_per_credit": 0.00199992 } ]}为什么我们做不了 gate
Section titled “为什么我们做不了 gate”Genspark Super Agent 的循环完全跑在 Genspark 自己的云端 VM 里。客户的 egress proxy 永远看不到 LLM API 调用的 payload —— Genspark 的 VM 是从它自己的网络边界内拨向 OpenAI / Anthropic / 其他 provider 的。管理端 API 只暴露事后的 credit 聚合,没有任何 pre-call hook。
这就是 SpendGuard 框架覆盖分析里的 Archetype IV:一个完全托管的云端 agent,客户对网络零可见性。唯一可行的集成方式就是对账 —— 事后把账单拉下来,在 dashboard 上呈现,在跨越阈值时告警。除非 Genspark 拿出 pre-call webhook,否则 gate 在架构上根本不可能。
Operator 调度
Section titled “Operator 调度”导入器是个周期性 worker,不是 daemon。用 cron、Kubernetes CronJob 或你环境里任何能调度的东西把它跑起来:
# Kubernetes CronJob example — pull every hour.apiVersion: batch/v1kind: CronJobmetadata: name: spendguard-importer-gensparkspec: schedule: "0 * * * *" jobTemplate: spec: template: spec: containers: - name: importer image: ghcr.io/m24927605/agentic-spendguard/importer-genspark:latest env: - name: GENSPARK_API_TOKEN valueFrom: secretKeyRef: name: genspark-admin-token key: token args: - --mode=live - --tenant=$(SPENDGUARD_TENANT_ID) - --budget=$(SPENDGUARD_BUDGET_ID) restartPolicy: OnFailure你在 dashboard 上看到什么
Section titled “你在 dashboard 上看到什么”- 每个 task、每个 workspace 的预估 $ —— 用
pricing_version打标。 - 消耗的 credits(永远有,未知 plan 的行也有)。
reservation_source = subscription_meter过滤器把 Genspark 花费和 Devin、Claude Code Pro / Codex subscription-meter 行归到一起(这些是跳过 BYOK ledger 的 advisory 行)。- Plan 级别的 pivot:
plusvsprovspremiumvs 未知。 - 当 vendor 填了 task category 时,按它拆分(
research/code_generation/ …)。
- Crate 脚手架 + Cargo.toml:
services/importer_genspark/Cargo.toml—— 排除在 workspace 外,publish = false,livefeature gate 起来。 - 纯换算:
services/importer_genspark/src/import_record.rs(import_record_to_audit_row)—— 无 I/O,不读时钟。 - 价格表:
services/importer_genspark/src/credit_price_table.rs—— 饱和运算,拒绝 NaN/Infinity/负数输入。 - CloudEvent builder:
services/importer_genspark/src/cloudevent_envelope.rs—— 通过tests/golden/cloudevent_v1alpha1_*.json做 golden 测试。 - Fixture loader:
services/importer_genspark/src/fixture_loader.rs—— 在 parse 时硬拒非 synthetic 的 ID(T9)。 - Live client:
services/importer_genspark/src/live/client.rs—— 仅 rustls 的reqwestwrapper,带类型化的 401/403/429/5xx 错误,外加严格的 三路 token gate(missing / empty / too-short)。 - Migration:
services/ledger/migrations/0061_import_source_widen_genspark.sql—— 把已有的audit_outbox.import_sourceCHECK 放宽,纳入genspark_team_api。
完整的设计推理 + slice 级拆解见
docs/specs/coverage/D16_genspark_importer/design.md。