-
feat(core): surface accountType + per-user ACL on SHARED connected accounts (#3392)
发布于
2026-05-11 20:03:22 +00:00 Summary
SDK wrappers for the Apollo-side Shared Accounts feature shipped across
four Hermes PRs:- hermes#9860 —
account_typeenum (PRIVATE / SHARED) + pin-only invariant - hermes#9882 — echo
account_typein create-flow responses - hermes#9887 — echo
account_typein internal link-session GET (no public docs surface) - hermes#9902 —
per-user ACL for SHARED connections, nested under
acl_config_for_shared
Ready for review — Apollo, the OpenAPI spec, and
@composio/client@0.1.0-alpha.71(published from
composio-base-ts#77)
all carry the merged contract.Implemented
Types (
types/connectedAccounts.types.ts)ConnectedAccountTypesconstants +ConnectedAccountTypeSchemaZod
enum +ConnectedAccountTypeTS type.ConnectedAccountAclConfigSchema(input — all 3 fields optional) and
ConnectedAccountAclConfigResponseSchema(output — fields populated
when block visible). Per-list cap 1000 entries, per-userIdlength
cap 1..256 chars — matches Apollo'saclConfig.ts.- Extended
CreateConnectedAccountLinkOptionsSchemawithaccountType
aclConfigForShared(nested object).
- Extended
ConnectedAccountRetrieveResponseSchemawithaccountType+
optionalaclConfigForShared(undefinedwhen caller can't see ACL —
distinguishes hidden-ACL from default-state). - New
UpdateConnectedAccountAclParamsSchema, refined to require ≥1
field (rejects no-op{}). Inline comment notes the ZodEffects.shape
quirk for future maintainers.
Model (
models/ConnectedAccounts.ts)link(userId, authConfigId, options)— forwardsaccountTypeand
serializesaclConfigForSharedto the nestedacl_config_for_shared
wire block via a new internalserializeAclConfigForWirehelper.
Default behaviour (no options) creates a PRIVATE connection exactly as
today.- New
updateAcl(nanoid, params)— wraps
client.connectedAccounts.patch(PATCH/connected_accounts/{id}),
serialising flat camelCase params into the nested wire block. PATCH
semantics: omit a field to leave unchanged; pass[]to clear an
allow/deny list. JSDoc flags the empty-array footgun
(notAllowedUserIds: []silently re-grants previously-blocked users).
ReturnsConnectedAccountPatchResponseto match sibling-method
ergonomics. - Clarifying JSDoc on existing
update()pointing readers at
updateAcl()for ACL and noting the historical "alias and/or
credentials" doc is inaccurate (separate cleanup PR).
Model (
models/ToolRouterSession.ts)authorize(toolkit, options)— options gainaccountType+
aclConfigForShared. The/tool_router/session/{session_id}/link
endpoint accepts the same fields, so a SHARED connection with an ACL can
be created in one call from inside a tool-router session.
Transformer (
utils/transformers/connectedAccounts.ts)- Maps
account_type→accountType. - Maps
acl_config_for_shared(nested) →aclConfigForShared(nested
camelCase). Forwardsundefinedwhen the block is absent so callers can
distinguish "I can't see the ACL" (cookie caller, non-creator) from
"ACL is the default deny-by-default state".
Errors (
errors/ConnectedAccountsErrors.ts)ComposioAclOnlyForSharedError(400) — wired at three call sites:
link(),updateAcl(),ToolRouterSession.authorize(). Substring
match on'acl_config_for_shared is only valid on SHARED'(verified
against Apollo'screateConnectedAccount.tsin hermes#9902).ComposioSharedAccessDeniedError(403) — exported, not yet wrapped
by SDK code. Will be wrapped whenTools.execute()/direct-execute
error mapping lands in a follow-up. For now, callers seeing this from a
rawBadRequestError/ForbiddenErrorcan match on message substring.ComposioSharedConnectionNotAccessibleError(400) — same status. Will
be wrapped whenToolRouterSession.create()/update()
session-validator error mapping lands.
Tests (
test/connectedAccounts/connectedAccounts.test.ts)- 12 new behaviour tests covering
link()ACL forwarding (5),
updateAcl()body construction + empty-object refine + AclOnlyForShared
mapping (6), andlink()error-mapping fallback (1). 911 tests pass (up
from 899).
Changeset
.changeset/connected-accounts-acl.md—@composio/core: minor
(additive surface, no breaking changes), describes the nested shape and
resolution rule.
Not implemented (deliberate scope cuts)
Skipped Why Where it lands Python SDK mirror composio-client@1.38.0(fromcomposio-base-py#66) now exposes the same fields. Better as its own PR scoped to python/composio/so Python's release cadence stays independent.Separate PR, same release train (called out in changeset). Docs cookbook entry ("share a connection with specific users") Needs @composio/coreto be published before the TS code samples cantwoslash-typecheck against the new methods.Separate PR under docs/content/cookbooks/.docs/public/openapi*.jsonspec refreshAuto-PR #3399 already opened against nextwith the post-9902 spec.Mergeable independently from this PR. update()→ real PATCH endpointPre-existing — JSDoc says "alias and/or credentials" but implementation calls updateStatus. Thenew JSDoc explicitly disclaims it; the behaviour-change rewrite is unrelated to ACL. Separate cleanup PR. ** ComposioSharedAccessDeniedError+ComposioSharedConnectionNotAccessibleErrorwiring**Need to map at Tools.execute()andToolRouterSession.create()/update()/patch().Out of scope for a connected-accounts PR — the failure modes are in different SDK touchpoints. Exported now so the public-API contract is stable, JSDocs explicit about when they'll start being thrown. Follow-up PR scoped to execute/session error mapping. Internal link-session GET (hermes#9887) Endpoint is /api/v3/internal/...and filtered out of the public spec bydocs/scripts/fetch-openapi.mjs. Dashboard UI consumes it, not the SDK.N/A — internal only. Cast tightening / shared wire-ACL fragment (review P3-13, P3-14) Casts and local body types are gone (commit 483af3607). Sharedfragment was already extracted as AclConfigWireShapein commit752efd886— now removed since it lives in the generated types.Done. Duplicate-account guard extraction (review P3-16) Reviewer explicitly says "parity-by-copy is fine for this PR" — link()andinitiate()share a 14-line guard. ExtractingassertNoActiveConnections()is mechanical and unrelated.Follow-up. Commits
84a3a074
— initial draft against the flat-field shape described in 9902's PR
body.
2.
752efd88
— reshape to the nestedaclConfigForSharedshape that actually merged
in 9902. Verified againststaging-backend.composio.dev's spec.
3.
483af3607
— bump@composio/clientto0.1.0-alpha.71, drop the three casts +
local body types, apply the 9 in-scope multi-agent review fixes, wire
ComposioAclOnlyForSharedErrorat three call sites, add 12 behaviour
tests.Test plan
pnpm --filter @composio/core typecheck— cleanpnpm --filter @composio/core test— 911/911 pass (12 new, 899
pre-existing)pnpm --filter @composio/core build—publint+attwclean- Wire contract spot-checked against prod spec for all five
endpoints (POST link, POST connected_accounts, PATCH, GET single, GET
list, tool_router/session link) - All forward-compat casts removed; SDK now uses
LinkCreateParams,
ConnectedAccountPatchParams,SessionLinkParamsfrom the generated
client directly - Manual smoke (reviewer or merger): create SHARED →
updateAcl({ allowAllUsers: true, notAllowedUserIds: ['user_bob'] })→
verifyget()returnsaclConfigForSharedfor creator, returns
undefinedfor non-creator.
🤖 Generated with Claude Code
下载附件
- hermes#9860 —