portage-ucp-client 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 96ae3a5da5ef868663d2b296539ef3812249479031f6237f0c4dbb5679dda268
4
- data.tar.gz: 7b132dc0d5c2152a2bba93db9629e5c897331a5360d95894cc84028fb9052ee9
3
+ metadata.gz: 8ca449d6a513f5b79171362c5fbd7830812fecf99abc5b60d88e98c6362faf10
4
+ data.tar.gz: fa8e0a0f2a00f3b62431303aafabfb94228e9fecae184c1d5ab9df3752240b46
5
5
  SHA512:
6
- metadata.gz: 88439c696a4d6c6b4ead359c4b463b00fc07f02e811774096e745d7e95880b45b67ec3d948c5beffea0f0bd56456834305c774cc4d2b0a7281846840fd526bc3
7
- data.tar.gz: d0965725d9e6914ae0bd19a0f2e8acabd182c943d01d0cb810458e3edc9d9d3001cf67bea1931eca3258b0315f32328e47b156a752c5f935bc07772d1ac9d0b3
6
+ metadata.gz: 15a8962035eef888342055b2307304c89fa97e180c1a2ebb70e9ae25cbf8bd35212d95f1b83a09afa0d27c0a6003bba92b3a844fc58145b04d0eed759fc4d366
7
+ data.tar.gz: ed79009feffcc06418784907007b210250edc1e95182c48a8c8408f8de3db226da082cc55f3d50bc532707ebe682618a4b7040963001c963c068497a12a79364
data/CHANGELOG.md CHANGED
@@ -4,7 +4,34 @@ All notable changes to this project are documented here. Format loosely follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.0.0/); this project is
5
5
  pre-1.0, so APIs may still shift between minor versions.
6
6
 
7
- ## [0.1.0] - Unreleased
7
+ ## [Unreleased]
8
+
9
+ ## [0.3.0] - 2026-09-14
10
+
11
+ - `Session` gains `#create_payment_enrollment(idempotency_key: nil)` /
12
+ `#get_payment_enrollment(enrollment_id:)`, covering the new
13
+ `app.portage-ucp.payment_enrollment` capability (docs/plans/agentic-payments.md
14
+ Phase 1). `create_payment_enrollment` is added to `MUTATING_ACTIONS`, so a
15
+ caller that omits `idempotency_key:` still gets one generated.
16
+ - Every transport (http, stdio, loopback) and `Session` grow an optional
17
+ `meta:` kwarg, threaded through the same way `correlation_id` already
18
+ reaches the server from `traceparent` — additive only, no caller passing
19
+ `meta:` is required to change.
20
+ - Widened the `portage-ucp` dependency pin from `~> 0.4` to `~> 0.5` —
21
+ `#create_payment_enrollment`/`#get_payment_enrollment` need the
22
+ `app.portage-ucp.payment_enrollment` capability, only present from
23
+ `portage-ucp` 0.5.0 on.
24
+
25
+ ## [0.2.0] - 2026-08-21
26
+
27
+ - `Session#create_checkout`/`#update_checkout` gain an optional `fulfillment:`
28
+ param, passed through only when given. Only exercised end-to-end over the
29
+ loopback transport (an in-process `Adapter`, taking a real
30
+ `Portage::Ucp::CheckoutFulfillment`); the stdio/HTTP wire shape for
31
+ `fulfillment` has no real UCP server to verify it against yet, so it's not
32
+ wired there.
33
+
34
+ ## [0.1.0] - 2026-08-14
8
35
 
9
36
  - Initial pre-release. Client-side SDK — loopback, stdio, and Streamable HTTP
10
37
  transports behind one `Session` interface.
@@ -18,7 +18,7 @@ module Portage
18
18
  # must branch on it themselves.
19
19
  class Session
20
20
  MUTATING_ACTIONS = %w[create_cart update_cart cancel_cart create_checkout update_checkout
21
- complete_checkout cancel_checkout].freeze
21
+ complete_checkout cancel_checkout create_payment_enrollment].freeze
22
22
 
23
23
  # @param capabilities [Array<String>, nil] reverse-domain capability
24
24
  # names advertised by the server, when known upfront (Client.discover
@@ -38,51 +38,76 @@ module Portage
38
38
  capabilities&.include?(capability_name)
39
39
  end
40
40
 
41
- def search_catalog(query:, limit: 20) = call("search_catalog", query: query, limit: limit)
42
- def get_product(product_id:) = call("get_product", product_id: product_id)
41
+ def search_catalog(query:, limit: 20,
42
+ meta: nil)
43
+ call("search_catalog", meta: meta, query: query, limit: limit)
44
+ end
45
+
46
+ def get_product(product_id:, meta: nil) = call("get_product", meta: meta, product_id: product_id)
47
+ def lookup_catalog(product_ids:, meta: nil) = call("lookup_catalog", meta: meta, product_ids: product_ids)
43
48
 
44
- def get_cart(cart_id:) = call("get_cart", cart_id: cart_id)
49
+ def get_cart(cart_id:, meta: nil) = call("get_cart", meta: meta, cart_id: cart_id)
45
50
 
46
- def create_cart(line_items:, idempotency_key: nil)
47
- call("create_cart", line_items: line_items, idempotency_key: idempotency_key)
51
+ def create_cart(line_items:, idempotency_key: nil, meta: nil)
52
+ call("create_cart", meta: meta, line_items: line_items, idempotency_key: idempotency_key)
48
53
  end
49
54
 
50
- def update_cart(cart_id:, line_items:, idempotency_key: nil)
51
- call("update_cart", cart_id: cart_id, line_items: line_items, idempotency_key: idempotency_key)
55
+ def update_cart(cart_id:, line_items:, idempotency_key: nil, meta: nil)
56
+ call("update_cart", meta: meta, cart_id: cart_id, line_items: line_items, idempotency_key: idempotency_key)
52
57
  end
53
58
 
54
- def cancel_cart(cart_id:, idempotency_key: nil)
55
- call("cancel_cart", cart_id: cart_id, idempotency_key: idempotency_key)
59
+ def cancel_cart(cart_id:, idempotency_key: nil, meta: nil)
60
+ call("cancel_cart", meta: meta, cart_id: cart_id, idempotency_key: idempotency_key)
56
61
  end
57
62
 
58
- def create_checkout(line_items:, idempotency_key: nil)
59
- call("create_checkout", line_items: line_items, idempotency_key: idempotency_key)
63
+ # `fulfillment:` (dev.ucp.shopping.fulfillment) is only exercised over
64
+ # the loopback transport today (Portage::Cli::Buy's own-store adapter
65
+ # path) — passed straight through as whatever value the caller built
66
+ # (a Portage::Ucp::CheckoutFulfillment for loopback). Over stdio/HTTP
67
+ # it would need a JSON wire shape this gem doesn't build yet, so
68
+ # callers on those transports should leave it nil.
69
+ def create_checkout(line_items:, idempotency_key: nil, fulfillment: nil, meta: nil)
70
+ call("create_checkout", meta: meta, line_items: line_items, idempotency_key: idempotency_key,
71
+ **(fulfillment ? { fulfillment: fulfillment } : {}))
60
72
  end
61
73
 
62
- def get_checkout(checkout_id:) = call("get_checkout", checkout_id: checkout_id)
74
+ def get_checkout(checkout_id:, meta: nil) = call("get_checkout", meta: meta, checkout_id: checkout_id)
63
75
 
64
- def update_checkout(checkout_id:, line_items:, idempotency_key: nil)
65
- call("update_checkout", checkout_id: checkout_id, line_items: line_items, idempotency_key: idempotency_key)
76
+ def update_checkout(checkout_id:, line_items:, idempotency_key: nil, fulfillment: nil, meta: nil)
77
+ call("update_checkout", meta: meta, checkout_id: checkout_id, line_items: line_items,
78
+ idempotency_key: idempotency_key,
79
+ **(fulfillment ? { fulfillment: fulfillment } : {}))
66
80
  end
67
81
 
68
- def complete_checkout(checkout_id:, payment_token:, idempotency_key: nil)
82
+ def complete_checkout(checkout_id:, payment_token:, idempotency_key: nil, meta: nil)
69
83
  Portage::Ucp::PaymentTokenGuard.validate!(payment_token)
70
- call("complete_checkout", checkout_id: checkout_id, payment_token: payment_token,
84
+ call("complete_checkout", meta: meta, checkout_id: checkout_id, payment_token: payment_token,
71
85
  idempotency_key: idempotency_key)
72
86
  end
73
87
 
74
- def cancel_checkout(checkout_id:, idempotency_key: nil)
75
- call("cancel_checkout", checkout_id: checkout_id, idempotency_key: idempotency_key)
88
+ def cancel_checkout(checkout_id:, idempotency_key: nil, meta: nil)
89
+ call("cancel_checkout", meta: meta, checkout_id: checkout_id, idempotency_key: idempotency_key)
90
+ end
91
+
92
+ def get_order(order_id:, meta: nil) = call("get_order", meta: meta, order_id: order_id)
93
+ def link_identity(oauth_token:, meta: nil) = call("link_identity", meta: meta, oauth_token: oauth_token)
94
+
95
+ # app.portage-ucp.payment_enrollment (Portage extension, §ref
96
+ # docs/plans/agentic-payments.md Phase 1) — not every adapter
97
+ # advertises this, check #advertises? first.
98
+ def create_payment_enrollment(idempotency_key: nil, meta: nil)
99
+ call("create_payment_enrollment", meta: meta, idempotency_key: idempotency_key)
76
100
  end
77
101
 
78
- def get_order(order_id:) = call("get_order", order_id: order_id)
79
- def link_identity(oauth_token:) = call("link_identity", oauth_token: oauth_token)
102
+ def get_payment_enrollment(enrollment_id:, meta: nil)
103
+ call("get_payment_enrollment", meta: meta, enrollment_id: enrollment_id)
104
+ end
80
105
 
81
106
  private
82
107
 
83
- def call(action, **arguments)
108
+ def call(action, meta: nil, **arguments)
84
109
  arguments[:idempotency_key] ||= SecureRandom.uuid if MUTATING_ACTIONS.include?(action)
85
- @transport.call_tool(name: action, arguments: arguments)
110
+ @transport.call_tool(name: action, arguments: arguments, meta: meta)
86
111
  end
87
112
  end
88
113
  end
@@ -12,8 +12,8 @@ module Portage
12
12
  @client.connect
13
13
  end
14
14
 
15
- def call_tool(name:, arguments:)
16
- response = @client.call_tool(name: name, arguments: arguments)
15
+ def call_tool(name:, arguments:, meta: nil)
16
+ response = @client.call_tool(name: name, arguments: arguments, meta: meta)
17
17
  ToolResult.extract(response, symbol_keys: false)
18
18
  end
19
19
  end
@@ -15,10 +15,11 @@ module Portage
15
15
  @next_id = 0
16
16
  end
17
17
 
18
- def call_tool(name:, arguments:)
18
+ def call_tool(name:, arguments:, meta: nil)
19
19
  @next_id += 1
20
20
  response = @server.handle(
21
- { jsonrpc: "2.0", id: @next_id, method: "tools/call", params: { name: name, arguments: arguments } }
21
+ { jsonrpc: "2.0", id: @next_id, method: "tools/call",
22
+ params: { name: name, arguments: arguments, **(meta ? { _meta: meta } : {}) } }
22
23
  )
23
24
  ToolResult.extract(response, symbol_keys: true)
24
25
  end
@@ -12,8 +12,8 @@ module Portage
12
12
  @client.connect
13
13
  end
14
14
 
15
- def call_tool(name:, arguments:)
16
- response = @client.call_tool(name: name, arguments: arguments)
15
+ def call_tool(name:, arguments:, meta: nil)
16
+ response = @client.call_tool(name: name, arguments: arguments, meta: meta)
17
17
  ToolResult.extract(response, symbol_keys: false)
18
18
  end
19
19
  end
@@ -1,7 +1,7 @@
1
1
  module Portage
2
2
  module Ucp
3
3
  module Client
4
- VERSION = "0.1.0".freeze
4
+ VERSION = "0.3.0".freeze
5
5
  end
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: portage-ucp-client
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Whitbread
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-14 00:00:00.000000000 Z
11
+ date: 2026-09-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mcp
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '0.1'
33
+ version: '0.5'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '0.1'
40
+ version: '0.5'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement