hermes-agent/agent
Siddharth Balyan 59529afee0
feat(billing): carry the payment-method union through to clients (#71542)
* feat(billing): add payment_method union to the billing-state wire type

* feat(billing): carry the payment-method union through the gateway

NAS now sends a typed `paymentMethod` union on /api/billing/state alongside
the legacy `card` field. The gateway parses payloads field-by-field, so the
new field was dropped on the floor before reaching TUI/Desktop.

Parse it into PaymentMethodInfo and re-emit it as snake_case `payment_method`,
matching the translation the rest of this payload already does. The payment
method id is deliberately not carried through — clients have no use for it.

No client rendering changes: `card` stays populated for cards, so every
existing consumer behaves exactly as before and the new field is inert until
a surface opts into reading it.

* fix(billing): send only the fields each payment-method kind declares

The serializer emitted every key for every kind, so a Link method went out
carrying brand, last4 and wallet set to null. That contradicts the shared
type, where each kind declares its own fields: a client testing `'brand' in
pm` would read every Link method as a card, and one trusting the declared
non-null `brand` could crash on it.

Send each kind's own fields, and forward an unrecognized kind by name alone
so a client that predates it can still say something honest. The shared type
gains the matching fallback arm its own comment already promised.

Tests now follow a payload from the server response through to the client
wire for each kind, rather than checking parsing and serializing separately —
which is why the old expectation locked in the wrong shape without noticing.

* fix(billing): keep the payment-method kind narrowable

Typing the fallback arm's kind as `string & {}` borrowed a trick that only
works on unions of plain strings. On a union of objects it makes the
discriminant non-literal, so TypeScript stops narrowing on every arm — even
`if (pm.kind === 'card')` no longer gives you `brand`. The first client to
use this would have hit a compile error and reached for a cast.

An unrecognised kind now arrives as `unknown`, carrying the real name
alongside it, so every arm has a literal discriminant. A type-level test
pins this: it fails to compile if the discriminant stops narrowing.

The parser settles which kind it is, the way the card parser already does,
so the record cannot hold fields that do not belong to its kind and the
serializer no longer re-checks. The type comment also stops claiming `card`
is a safe signal — it is null for Link, so `!card` does not mean "nothing on
file".
2026-07-27 01:49:43 +05:30
..
lsp fix(windows): sweep remaining unguarded text-mode subprocess sites codebase-wide 2026-07-24 11:45:57 -07:00
pet
proxy_sources
secret_sources
transports fix(codex): scope 24h retention to Bedrock Mantle 2026-07-24 15:54:08 -07:00
__init__.py
account_usage.py
agent_init.py
agent_runtime_helpers.py
anthropic_adapter.py
async_utils.py
aux_accounting.py
auxiliary_client.py
azure_identity_adapter.py
background_review.py
battery.py
bedrock_adapter.py
billing_links.py
billing_usage.py
billing_view.py
bounded_response.py
browser_provider.py
browser_registry.py
chat_completion_helpers.py
codex_responses_adapter.py
codex_runtime.py
coding_context.py
context_breakdown.py
context_compressor.py
context_engine.py
context_references.py
conversation_compression.py
conversation_loop.py
copilot_acp_client.py
credential_persistence.py
credential_pool.py
credential_sources.py
credits_tracker.py
curator.py
curator_backup.py
delegation_context.py
display.py
error_classifier.py
errors.py
file_safety.py
gemini_native_adapter.py
gemini_schema.py
i18n.py
image_gen_provider.py
image_gen_registry.py
image_routing.py
insights.py
iteration_budget.py
jiter_preload.py
kanban_stop.py
learn_prompt.py
learning_graph.py
learning_graph_render.py
learning_mutations.py
lmstudio_reasoning.py
manual_compression_feedback.py fix(compress): classify unconfirmed lock-acquire failures and cover all manual-compress surfaces 2026-07-23 08:19:14 -07:00
markdown_tables.py fix(cli): vertical fallback for markdown tables wider than terminal (#23948) 2026-05-11 16:49:13 -07:00
memory_manager.py
memory_provider.py
message_content.py
message_sanitization.py
moa_loop.py
moa_trace.py
model_metadata.py
models_dev.py
moonshot_schema.py fix(agent): inject empty required array on Moonshot object schemas 2026-07-20 11:05:52 -07:00
nous_rate_guard.py
onboarding.py
oneshot.py
plugin_llm.py
portal_tags.py
process_bootstrap.py
prompt_builder.py fix(P1+P2): marker names skill_view(name='X') + DEDUP rule for repeated [SKILL_PRUNED] markers 2026-07-23 16:58:06 -07:00
prompt_caching.py
rate_limit_tracker.py
reactions.py
reasoning_timeouts.py
redact.py
replay_cleanup.py
retry_utils.py
runtime_cwd.py
secret_scope.py
shell_hooks.py
skill_bundles.py
skill_commands.py feat(skills): describe a /skill turn the way the user typed it 2026-07-26 02:58:12 -05:00
skill_preprocessing.py
skill_utils.py
ssl_guard.py
ssl_verify.py
stream_diag.py
stream_single_writer.py
subdirectory_hints.py
subscription_view.py
system_prompt.py
think_scrubber.py
thinking_timeout_guidance.py
thread_scoped_output.py
title_generator.py fix(sessions): stop titling a session after the skill it invoked 2026-07-26 02:58:16 -05:00
tool_dispatch_helpers.py
tool_executor.py
tool_guardrails.py
tool_result_classification.py
trace_upload.py
trajectory.py
transcription_provider.py
transcription_registry.py
tts_provider.py
tts_registry.py
turn_context.py
turn_finalizer.py
turn_retry_state.py
usage_pricing.py
verification_evidence.py
verification_stop.py
verify_hooks.py
vertex_adapter.py
video_gen_provider.py
video_gen_registry.py
web_search_provider.py
web_search_registry.py