get_assets 的 currency 过滤在路由层被静默丢弃,真实 provider 恒返回全部币种
问题与复现
get_assets 工具(能力 portfolio.assets)在四处都声明了「可按币种过滤」:
| 层 |
位置 |
声明 |
| 工具 schema |
packages/shared/src/capabilities/manifests/phase-two.ts:387 |
currency: Type.Optional(Type.String({ description: 'Filter by currency, e.g. USD, HKD' })) |
| 能力 fetchers |
packages/shared/src/capabilities/fetchers.ts:93、providers/router-fetchers.ts:95 |
getAssets: (currency?: string) => Promise<AccountAssets[]> |
| 路由入口 |
packages/shared/src/providers/router-fetchers.ts:199 |
getAssets: (currency) => fetch(router, 'portfolio.assets', { currency }) |
| 底层 CLI |
packages/longbridge-tools/src/tools/assets.ts:6-9 |
if (currency) args.push('--currency', currency) |
但链路在 ProviderRouter 处断掉:
// packages/shared/src/providers/router.ts:505-512
private async invokeBroker(provider, capabilityId, input, signal) {
const accountId = readAccountId(input); // 只读 accountId
switch (capabilityId) {
...
case 'portfolio.assets':
return provider.getAssets(accountId, signal); // currency 未传
而长桥 broker 实现也没有 currency 形参,于是底层永远以 getAssets(undefined) 调用:
// packages/shared/src/providers/longbridge/broker.ts:102
getAssets(accountId?: string, signal?: AbortSignal) {
return runProviderCall<AccountAssets[]>(() => getAssets(), signal);
}
反例:调用 get_assets { currency: 'HKD' }
- 实际:
router.execute('portfolio.assets', { currency: 'HKD' }) → provider 收到 currency === undefined → 底层执行 longbridge assets --format json → 返回全部报告币种。
- 期望:底层执行
longbridge assets --currency HKD --format json,结果只含 HKD 条目。
AccountAssets 是按报告币种一条记录的(packages/core/src/account.ts:90,字段 currency),工具描述本身也写着 “per-currency cash breakdown”,因此该过滤是有意义的入参而不是装饰。
为什么一直没有被发现
packages/longbridge-tools/src/phase-two.test.ts:244 有 getAssets forwards optional currency,但它直接调用底层函数 getAssets('HKD'),覆盖不到 shared 的 broker provider / router 这一层。
packages/shared/src/providers/providers.test.ts:104 的替身 getAssets(accountId?, signal?) 只返回 [],不校验参数是否抵达。
packages/shared/src/capabilities/phase-two.test.ts 的 fake fetchers 用 getAssets: async () => [],同样不校验。
即:每层单测各自「签名对得上」,但没有一条断言 currency 真的走完全程。
预期与修复方向
让 currency 走完全程,且不破坏既有的 getAssets(accountId, signal) 调用形态:
packages/core/src/provider.ts:BrokerAccountProvider.getAssets 增加可选第三形参 currency?: string(可选参数,既有实现少传参仍然类型兼容)。
packages/shared/src/providers/router.ts:portfolio.assets 分支读取并透传 input.currency。
packages/shared/src/providers/longbridge/broker.ts:getAssets 接收 currency 并交给底层 CLI。
复现证据
- 环境:Windows 11 + Bun 1.4.2,main
8eca711130f7cd60c2e84c7e0e9bcee5d6bb384d。
- 新增回归测试
packages/shared/src/providers/broker-assets-currency.test.ts(5 例)。
- 修复前(main 原始字节):
2 pass / 3 fail,其中端到端一例为
Expected: ["HKD"] / Received: ["USD", "HKD"],参数一例为
Expected: { accountId: "A1", currency: "USD" } / Received: { accountId: "A1", currency: undefined }。
- 修复后:
5 pass / 0 fail。
bun test packages/shared/src/providers --isolate:120 pass / 0 fail。
bun test packages/core --isolate:51 pass / 0 fail。
bun run typecheck:core / i18n / shared / ui / electron 五个工作区 exit 0。
bun test packages/shared --isolate:1093 pass / 4 fail;4 项失败为既有基线
(ExperimentService ×1、langfuse ×1、ResearchService ×2),已做 A/B 对照:
把本次改动的三个生产文件还原为 main 原始字节后重跑同一批文件,失败项与数量逐条一致(52 pass / 4 fail)。
范围
get_assets 的 currency 过滤在路由层被静默丢弃,真实 provider 恒返回全部币种
问题与复现
get_assets工具(能力portfolio.assets)在四处都声明了「可按币种过滤」:packages/shared/src/capabilities/manifests/phase-two.ts:387currency: Type.Optional(Type.String({ description: 'Filter by currency, e.g. USD, HKD' }))packages/shared/src/capabilities/fetchers.ts:93、providers/router-fetchers.ts:95getAssets: (currency?: string) => Promise<AccountAssets[]>packages/shared/src/providers/router-fetchers.ts:199getAssets: (currency) => fetch(router, 'portfolio.assets', { currency })packages/longbridge-tools/src/tools/assets.ts:6-9if (currency) args.push('--currency', currency)但链路在
ProviderRouter处断掉:而长桥 broker 实现也没有 currency 形参,于是底层永远以
getAssets(undefined)调用:反例:调用
get_assets { currency: 'HKD' }router.execute('portfolio.assets', { currency: 'HKD' })→ provider 收到currency === undefined→ 底层执行longbridge assets --format json→ 返回全部报告币种。longbridge assets --currency HKD --format json,结果只含HKD条目。AccountAssets是按报告币种一条记录的(packages/core/src/account.ts:90,字段currency),工具描述本身也写着 “per-currency cash breakdown”,因此该过滤是有意义的入参而不是装饰。为什么一直没有被发现
packages/longbridge-tools/src/phase-two.test.ts:244有getAssets forwards optional currency,但它直接调用底层函数getAssets('HKD'),覆盖不到shared的 broker provider / router 这一层。packages/shared/src/providers/providers.test.ts:104的替身getAssets(accountId?, signal?)只返回[],不校验参数是否抵达。packages/shared/src/capabilities/phase-two.test.ts的 fake fetchers 用getAssets: async () => [],同样不校验。即:每层单测各自「签名对得上」,但没有一条断言 currency 真的走完全程。
预期与修复方向
让 currency 走完全程,且不破坏既有的
getAssets(accountId, signal)调用形态:packages/core/src/provider.ts:BrokerAccountProvider.getAssets增加可选第三形参currency?: string(可选参数,既有实现少传参仍然类型兼容)。packages/shared/src/providers/router.ts:portfolio.assets分支读取并透传input.currency。packages/shared/src/providers/longbridge/broker.ts:getAssets接收currency并交给底层 CLI。复现证据
8eca711130f7cd60c2e84c7e0e9bcee5d6bb384d。packages/shared/src/providers/broker-assets-currency.test.ts(5 例)。2 pass / 3 fail,其中端到端一例为Expected: ["HKD"] / Received: ["USD", "HKD"],参数一例为Expected: { accountId: "A1", currency: "USD" } / Received: { accountId: "A1", currency: undefined }。5 pass / 0 fail。bun test packages/shared/src/providers --isolate:120 pass / 0 fail。bun test packages/core --isolate:51 pass / 0 fail。bun run typecheck:core / i18n / shared / ui / electron 五个工作区 exit 0。bun test packages/shared --isolate:1093 pass / 4 fail;4 项失败为既有基线(ExperimentService ×1、langfuse ×1、ResearchService ×2),已做 A/B 对照:
把本次改动的三个生产文件还原为 main 原始字节后重跑同一批文件,失败项与数量逐条一致(52 pass / 4 fail)。
范围
portfolio.assets一条链路的 currency 透传,不改其他 broker 能力的行为。