@agirails/sdk 3.4.1 → 3.5.3
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.
- package/dist/builders/QuoteBuilder.d.ts +9 -3
- package/dist/builders/QuoteBuilder.d.ts.map +1 -1
- package/dist/builders/QuoteBuilder.js +14 -3
- package/dist/builders/QuoteBuilder.js.map +1 -1
- package/dist/cli/commands/agent.d.ts +22 -0
- package/dist/cli/commands/agent.d.ts.map +1 -0
- package/dist/cli/commands/agent.js +209 -0
- package/dist/cli/commands/agent.js.map +1 -0
- package/dist/cli/index.js +5 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/config/networks.d.ts.map +1 -1
- package/dist/config/networks.js +7 -1
- package/dist/config/networks.js.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/level1/Agent.d.ts.map +1 -1
- package/dist/level1/Agent.js +1 -1
- package/dist/level1/Agent.js.map +1 -1
- package/dist/negotiation/BuyerOrchestrator.d.ts +72 -60
- package/dist/negotiation/BuyerOrchestrator.d.ts.map +1 -1
- package/dist/negotiation/BuyerOrchestrator.js +411 -380
- package/dist/negotiation/BuyerOrchestrator.js.map +1 -1
- package/dist/negotiation/MockChannel.d.ts +63 -0
- package/dist/negotiation/MockChannel.d.ts.map +1 -0
- package/dist/negotiation/MockChannel.js +175 -0
- package/dist/negotiation/MockChannel.js.map +1 -0
- package/dist/negotiation/NegotiationChannel.d.ts +142 -0
- package/dist/negotiation/NegotiationChannel.d.ts.map +1 -0
- package/dist/negotiation/NegotiationChannel.js +59 -0
- package/dist/negotiation/NegotiationChannel.js.map +1 -0
- package/dist/negotiation/ProviderOrchestrator.d.ts +85 -35
- package/dist/negotiation/ProviderOrchestrator.d.ts.map +1 -1
- package/dist/negotiation/ProviderOrchestrator.js +199 -49
- package/dist/negotiation/ProviderOrchestrator.js.map +1 -1
- package/dist/negotiation/ProviderPolicy.d.ts +51 -6
- package/dist/negotiation/ProviderPolicy.d.ts.map +1 -1
- package/dist/negotiation/ProviderPolicy.js +61 -9
- package/dist/negotiation/ProviderPolicy.js.map +1 -1
- package/dist/negotiation/RelayChannel.d.ts +59 -0
- package/dist/negotiation/RelayChannel.d.ts.map +1 -0
- package/dist/negotiation/RelayChannel.js +208 -0
- package/dist/negotiation/RelayChannel.js.map +1 -0
- package/dist/protocol/ACTPKernel.d.ts.map +1 -1
- package/dist/protocol/ACTPKernel.js +51 -1
- package/dist/protocol/ACTPKernel.js.map +1 -1
- package/dist/runtime/BlockchainRuntime.d.ts.map +1 -1
- package/dist/runtime/BlockchainRuntime.js +10 -2
- package/dist/runtime/BlockchainRuntime.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,44 +1,64 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ProviderOrchestrator — autonomous provider-side
|
|
2
|
+
* ProviderOrchestrator — autonomous provider-side negotiation flow.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* request (quote-or-skip, at what price), one submitQuote tx on-chain,
|
|
6
|
-
* one off-chain POST to the buyer's quote channel. Counter-offer
|
|
7
|
-
* evaluation is delegated to the policy engine; counter-counter
|
|
8
|
-
* strategies are Phase 3 territory and out of scope here.
|
|
4
|
+
* Two responsibilities:
|
|
9
5
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
6
|
+
* 1. Accept an incoming request → decide whether to quote → if yes,
|
|
7
|
+
* build + sign a QuoteMessage, anchor on-chain via submitQuote,
|
|
8
|
+
* post it on the NegotiationChannel for the buyer.
|
|
9
|
+
*
|
|
10
|
+
* 2. (3.5.0) Run a long-lived `start()` listener on the channel:
|
|
11
|
+
* every counter that arrives is evaluated against ProviderPolicy;
|
|
12
|
+
* based on `counter_strategy` we either auto-accept (build + post
|
|
13
|
+
* CounterAcceptMessage), auto-requote (build + post a new
|
|
14
|
+
* QuoteMessage with the conceded amount), or walk (log + drop).
|
|
15
|
+
*
|
|
16
|
+
* Symmetric to BuyerOrchestrator's channel-driven multi-round loop —
|
|
17
|
+
* together they implement the full AIP-2.1 §6 negotiation protocol
|
|
18
|
+
* without either party needing to host an HTTP endpoint.
|
|
13
19
|
*
|
|
14
20
|
* @module negotiation/ProviderOrchestrator
|
|
15
|
-
* @see Protocol/aips/AIP-2.1
|
|
21
|
+
* @see Protocol/aips/AIP-2.1.md §5.2 (provider quote flow)
|
|
22
|
+
* @see Protocol/aips/AIP-2.1.md §6 (NegotiationChannel)
|
|
16
23
|
*/
|
|
17
24
|
import { Signer } from 'ethers';
|
|
18
25
|
import { IACTPRuntime } from '../runtime/IACTPRuntime';
|
|
19
26
|
import { QuoteMessage } from '../builders/QuoteBuilder';
|
|
20
27
|
import { CounterOfferMessage } from '../builders/CounterOfferBuilder';
|
|
21
28
|
import { NonceManager } from '../utils/NonceManager';
|
|
22
|
-
import {
|
|
29
|
+
import { NegotiationChannel, Subscription } from './NegotiationChannel';
|
|
23
30
|
import { ProviderPolicy, IncomingRequest } from './ProviderPolicy';
|
|
24
31
|
export interface ProviderOrchestratorConfig {
|
|
25
32
|
policy: ProviderPolicy;
|
|
26
33
|
runtime: IACTPRuntime;
|
|
27
|
-
/** Provider's EOA signer — signs QuoteMessages. */
|
|
34
|
+
/** Provider's EOA signer — signs QuoteMessages + CounterAcceptMessages. */
|
|
28
35
|
signer: Signer;
|
|
29
|
-
/** Kernel address
|
|
36
|
+
/** Kernel address for EIP-712 domain. */
|
|
30
37
|
kernelAddress: string;
|
|
31
|
-
/** Chain id
|
|
38
|
+
/** Chain id (84532 or 8453). */
|
|
32
39
|
chainId: number;
|
|
33
40
|
/**
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
|
|
41
|
+
* Provider's DID — used for `subscribeAgent` filter on the channel
|
|
42
|
+
* AND as the `provider` field on outbound QuoteMessage /
|
|
43
|
+
* CounterAcceptMessage. Required for `start()`.
|
|
44
|
+
*/
|
|
45
|
+
providerDID?: string;
|
|
46
|
+
/**
|
|
47
|
+
* Persistent nonce manager. Defaults to in-memory (fine for dev;
|
|
48
|
+
* production should pass a FileBased one so restarts don't replay).
|
|
38
49
|
*/
|
|
39
50
|
nonceManager?: NonceManager;
|
|
40
|
-
/**
|
|
41
|
-
|
|
51
|
+
/**
|
|
52
|
+
* Negotiation channel. Required for `start()` long-running mode.
|
|
53
|
+
* Optional for the one-shot `quote()` method (caller may handle
|
|
54
|
+
* delivery via direct HTTP / IPFS / etc).
|
|
55
|
+
*/
|
|
56
|
+
negotiationChannel?: NegotiationChannel;
|
|
57
|
+
/**
|
|
58
|
+
* Logger for observability. Default: noop. `actp agent` wires this
|
|
59
|
+
* to its terminal Output.
|
|
60
|
+
*/
|
|
61
|
+
log?: (level: 'info' | 'warn' | 'error', msg: string) => void;
|
|
42
62
|
}
|
|
43
63
|
export type QuoteDecision = {
|
|
44
64
|
action: 'quote';
|
|
@@ -55,9 +75,9 @@ export type QuoteDecision = {
|
|
|
55
75
|
};
|
|
56
76
|
export interface QuoteResult {
|
|
57
77
|
decision: QuoteDecision;
|
|
58
|
-
/** Set when action === 'quote' and
|
|
78
|
+
/** Set when action === 'quote' and on-chain anchoring succeeded. */
|
|
59
79
|
quote?: QuoteMessage;
|
|
60
|
-
/** Set when
|
|
80
|
+
/** Set when channel post failed (on-chain still succeeded). */
|
|
61
81
|
channelError?: string;
|
|
62
82
|
}
|
|
63
83
|
export type CounterDecision = {
|
|
@@ -66,6 +86,10 @@ export type CounterDecision = {
|
|
|
66
86
|
} | {
|
|
67
87
|
action: 'reject';
|
|
68
88
|
reason: string;
|
|
89
|
+
} | {
|
|
90
|
+
action: 'requote';
|
|
91
|
+
amountBaseUnits: string;
|
|
92
|
+
reason: string;
|
|
69
93
|
};
|
|
70
94
|
export declare class ProviderOrchestrator {
|
|
71
95
|
private readonly policy;
|
|
@@ -74,35 +98,61 @@ export declare class ProviderOrchestrator {
|
|
|
74
98
|
private readonly signer;
|
|
75
99
|
private readonly kernelAddress;
|
|
76
100
|
private readonly chainId;
|
|
101
|
+
private readonly providerDID?;
|
|
77
102
|
private readonly nonceManager;
|
|
78
|
-
private readonly
|
|
103
|
+
private readonly negotiationChannel?;
|
|
79
104
|
private readonly quoteBuilder;
|
|
80
105
|
private readonly counterVerifier;
|
|
106
|
+
private readonly counterAcceptBuilder;
|
|
107
|
+
private readonly log;
|
|
108
|
+
/** Per-tx state for the multi-round counter listener. */
|
|
109
|
+
private readonly txStates;
|
|
110
|
+
/** Active channel subscription opened by `start()`. */
|
|
111
|
+
private channelSubscription?;
|
|
81
112
|
constructor(cfg: ProviderOrchestratorConfig);
|
|
82
113
|
/**
|
|
83
|
-
* Decide whether to quote.
|
|
84
|
-
* Use `quote()` below to act on the decision.
|
|
114
|
+
* Decide whether to quote. Pure policy — no chain, no channel.
|
|
85
115
|
*/
|
|
86
116
|
evaluateRequest(req: IncomingRequest): QuoteDecision;
|
|
87
117
|
/**
|
|
88
118
|
* Full quote flow: evaluate → build signed QuoteMessage → submit
|
|
89
|
-
* on-chain →
|
|
119
|
+
* on-chain → post on negotiationChannel.
|
|
120
|
+
*
|
|
121
|
+
* Channel post failure is non-fatal: on-chain anchor succeeded so
|
|
122
|
+
* buyer can still observe the quote, just won't see the off-chain
|
|
123
|
+
* signed body. Caller can retry channel post separately.
|
|
124
|
+
*/
|
|
125
|
+
quote(req: IncomingRequest, providerDID: string): Promise<QuoteResult>;
|
|
126
|
+
/**
|
|
127
|
+
* Subscribe to the negotiation channel and auto-respond to incoming
|
|
128
|
+
* counter-offers per `counter_strategy`. Idempotent — calling start()
|
|
129
|
+
* twice replaces the previous subscription.
|
|
130
|
+
*
|
|
131
|
+
* Returns a Subscription so the caller can stop the daemon cleanly.
|
|
90
132
|
*
|
|
91
|
-
*
|
|
92
|
-
* Off-chain POST failure → returns successfully with `channelError`
|
|
93
|
-
* set so caller knows to retry only the delivery step. Preserves the
|
|
94
|
-
* invariant "on-chain tx is what buyer observes regardless of our
|
|
95
|
-
* off-chain delivery".
|
|
133
|
+
* @throws if `negotiationChannel` or `providerDID` was not set in config.
|
|
96
134
|
*/
|
|
97
|
-
|
|
135
|
+
start(): Promise<Subscription>;
|
|
98
136
|
/**
|
|
99
|
-
*
|
|
100
|
-
|
|
137
|
+
* Stop the active channel subscription if any. Idempotent.
|
|
138
|
+
*/
|
|
139
|
+
stop(): void;
|
|
140
|
+
/**
|
|
141
|
+
* Verify + evaluate a buyer counter-offer. Returns the decision
|
|
142
|
+
* (accept / reject / requote with concession amount). Does NOT send
|
|
143
|
+
* any response — caller drives the next step. Use `start()` for
|
|
144
|
+
* autonomous operation.
|
|
145
|
+
*
|
|
146
|
+
* `lastQuoteAmountBaseUnits` — provider's most recent quote amount
|
|
147
|
+
* for this tx. On the first counter pass `counter.quoteAmount`.
|
|
148
|
+
*
|
|
149
|
+
* `requotesUsed` — defaults to 0; pass the real count for multi-round.
|
|
101
150
|
*
|
|
102
151
|
* @throws if the counter signature / band / expiry fails verify.
|
|
103
152
|
*/
|
|
104
|
-
evaluateCounter(counter: CounterOfferMessage): Promise<CounterDecision>;
|
|
153
|
+
evaluateCounter(counter: CounterOfferMessage, lastQuoteAmountBaseUnits?: string, requotesUsed?: number): Promise<CounterDecision>;
|
|
105
154
|
/** Read-only policy accessor for UIs and tests. */
|
|
106
155
|
getPolicy(): ProviderPolicy;
|
|
156
|
+
private _handleIncomingCounter;
|
|
107
157
|
}
|
|
108
158
|
//# sourceMappingURL=ProviderOrchestrator.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderOrchestrator.d.ts","sourceRoot":"","sources":["../../src/negotiation/ProviderOrchestrator.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"ProviderOrchestrator.d.ts","sourceRoot":"","sources":["../../src/negotiation/ProviderOrchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,48 +1,61 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* ProviderOrchestrator — autonomous provider-side
|
|
3
|
+
* ProviderOrchestrator — autonomous provider-side negotiation flow.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* request (quote-or-skip, at what price), one submitQuote tx on-chain,
|
|
7
|
-
* one off-chain POST to the buyer's quote channel. Counter-offer
|
|
8
|
-
* evaluation is delegated to the policy engine; counter-counter
|
|
9
|
-
* strategies are Phase 3 territory and out of scope here.
|
|
5
|
+
* Two responsibilities:
|
|
10
6
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
7
|
+
* 1. Accept an incoming request → decide whether to quote → if yes,
|
|
8
|
+
* build + sign a QuoteMessage, anchor on-chain via submitQuote,
|
|
9
|
+
* post it on the NegotiationChannel for the buyer.
|
|
10
|
+
*
|
|
11
|
+
* 2. (3.5.0) Run a long-lived `start()` listener on the channel:
|
|
12
|
+
* every counter that arrives is evaluated against ProviderPolicy;
|
|
13
|
+
* based on `counter_strategy` we either auto-accept (build + post
|
|
14
|
+
* CounterAcceptMessage), auto-requote (build + post a new
|
|
15
|
+
* QuoteMessage with the conceded amount), or walk (log + drop).
|
|
16
|
+
*
|
|
17
|
+
* Symmetric to BuyerOrchestrator's channel-driven multi-round loop —
|
|
18
|
+
* together they implement the full AIP-2.1 §6 negotiation protocol
|
|
19
|
+
* without either party needing to host an HTTP endpoint.
|
|
14
20
|
*
|
|
15
21
|
* @module negotiation/ProviderOrchestrator
|
|
16
|
-
* @see Protocol/aips/AIP-2.1
|
|
22
|
+
* @see Protocol/aips/AIP-2.1.md §5.2 (provider quote flow)
|
|
23
|
+
* @see Protocol/aips/AIP-2.1.md §6 (NegotiationChannel)
|
|
17
24
|
*/
|
|
18
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
26
|
exports.ProviderOrchestrator = void 0;
|
|
20
27
|
const QuoteBuilder_1 = require("../builders/QuoteBuilder");
|
|
21
28
|
const CounterOfferBuilder_1 = require("../builders/CounterOfferBuilder");
|
|
29
|
+
const CounterAcceptBuilder_1 = require("../builders/CounterAcceptBuilder");
|
|
22
30
|
const NonceManager_1 = require("../utils/NonceManager");
|
|
23
|
-
const
|
|
31
|
+
const NegotiationChannel_1 = require("./NegotiationChannel");
|
|
24
32
|
const ProviderPolicy_1 = require("./ProviderPolicy");
|
|
25
33
|
// ============================================================================
|
|
26
34
|
// Orchestrator
|
|
27
35
|
// ============================================================================
|
|
28
36
|
class ProviderOrchestrator {
|
|
29
37
|
constructor(cfg) {
|
|
38
|
+
/** Per-tx state for the multi-round counter listener. */
|
|
39
|
+
this.txStates = new Map();
|
|
30
40
|
this.policy = cfg.policy;
|
|
31
41
|
this.policyEngine = new ProviderPolicy_1.ProviderPolicyEngine(cfg.policy);
|
|
32
42
|
this.runtime = cfg.runtime;
|
|
33
43
|
this.signer = cfg.signer;
|
|
34
44
|
this.kernelAddress = cfg.kernelAddress;
|
|
35
45
|
this.chainId = cfg.chainId;
|
|
46
|
+
this.providerDID = cfg.providerDID;
|
|
36
47
|
this.nonceManager = cfg.nonceManager ?? new NonceManager_1.InMemoryNonceManager();
|
|
37
|
-
this.
|
|
48
|
+
this.negotiationChannel = cfg.negotiationChannel;
|
|
49
|
+
this.log = cfg.log ?? (() => undefined);
|
|
38
50
|
this.quoteBuilder = new QuoteBuilder_1.QuoteBuilder(this.signer, this.nonceManager);
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
this.counterVerifier = new CounterOfferBuilder_1.CounterOfferBuilder(this.signer, this.nonceManager);
|
|
51
|
+
this.counterVerifier = new CounterOfferBuilder_1.CounterOfferBuilder(); // verify-only
|
|
52
|
+
this.counterAcceptBuilder = new CounterAcceptBuilder_1.CounterAcceptBuilder(this.signer, this.nonceManager);
|
|
42
53
|
}
|
|
54
|
+
// --------------------------------------------------------------------------
|
|
55
|
+
// One-shot quote (caller-driven)
|
|
56
|
+
// --------------------------------------------------------------------------
|
|
43
57
|
/**
|
|
44
|
-
* Decide whether to quote.
|
|
45
|
-
* Use `quote()` below to act on the decision.
|
|
58
|
+
* Decide whether to quote. Pure policy — no chain, no channel.
|
|
46
59
|
*/
|
|
47
60
|
evaluateRequest(req) {
|
|
48
61
|
const result = this.policyEngine.evaluate(req);
|
|
@@ -61,27 +74,19 @@ class ProviderOrchestrator {
|
|
|
61
74
|
}
|
|
62
75
|
/**
|
|
63
76
|
* Full quote flow: evaluate → build signed QuoteMessage → submit
|
|
64
|
-
* on-chain →
|
|
77
|
+
* on-chain → post on negotiationChannel.
|
|
65
78
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* invariant "on-chain tx is what buyer observes regardless of our
|
|
70
|
-
* off-chain delivery".
|
|
79
|
+
* Channel post failure is non-fatal: on-chain anchor succeeded so
|
|
80
|
+
* buyer can still observe the quote, just won't see the off-chain
|
|
81
|
+
* signed body. Caller can retry channel post separately.
|
|
71
82
|
*/
|
|
72
|
-
async quote(req, providerDID
|
|
83
|
+
async quote(req, providerDID) {
|
|
73
84
|
const decision = this.evaluateRequest(req);
|
|
74
|
-
if (decision.action === 'skip')
|
|
85
|
+
if (decision.action === 'skip')
|
|
75
86
|
return { decision };
|
|
76
|
-
}
|
|
77
|
-
// Build + sign QuoteMessage. Amount stays in base units throughout
|
|
78
|
-
// (no float round-trip). Currency / decimals come from policy —
|
|
79
|
-
// hardcoding would silently mask a config drift if we ever support
|
|
80
|
-
// non-USDC tokens. QuoteBuilder validates USDC/6 today; other
|
|
81
|
-
// currencies will throw there until the builder grows support.
|
|
82
87
|
const now = Math.floor(Date.now() / 1000);
|
|
83
88
|
const currency = this.policyEngine.policyCurrency;
|
|
84
|
-
const decimals = currency.toUpperCase() === 'USDC' ? 6 : 6;
|
|
89
|
+
const decimals = currency.toUpperCase() === 'USDC' ? 6 : 6;
|
|
85
90
|
const quote = await this.quoteBuilder.build({
|
|
86
91
|
txId: req.txId,
|
|
87
92
|
provider: providerDID,
|
|
@@ -95,42 +100,187 @@ class ProviderOrchestrator {
|
|
|
95
100
|
chainId: this.chainId,
|
|
96
101
|
kernelAddress: this.kernelAddress,
|
|
97
102
|
});
|
|
98
|
-
// On-chain: INITIATED → QUOTED, hash stored in tx.metadata.
|
|
99
103
|
await this.runtime.submitQuote(req.txId, quote);
|
|
100
|
-
|
|
101
|
-
// Caller may omit consumerEndpoint when the transport is handled
|
|
102
|
-
// outside this orchestrator (e.g. Telegram, IPFS pubsub adapter).
|
|
103
|
-
if (consumerEndpoint) {
|
|
104
|
+
if (this.negotiationChannel) {
|
|
104
105
|
try {
|
|
105
|
-
await this.
|
|
106
|
+
await this.negotiationChannel.post(req.txId, { type: 'agirails.quote.v1', message: quote });
|
|
106
107
|
}
|
|
107
108
|
catch (err) {
|
|
108
|
-
return {
|
|
109
|
-
decision,
|
|
110
|
-
quote,
|
|
111
|
-
channelError: err instanceof Error ? err.message : String(err),
|
|
112
|
-
};
|
|
109
|
+
return { decision, quote, channelError: err instanceof Error ? err.message : String(err) };
|
|
113
110
|
}
|
|
114
111
|
}
|
|
112
|
+
// Seed per-tx state so a follow-up counter is evaluated with the
|
|
113
|
+
// right `lastQuote` baseline if the listener is running.
|
|
114
|
+
this.txStates.set(req.txId, {
|
|
115
|
+
lastQuote: quote,
|
|
116
|
+
requotesUsed: 0,
|
|
117
|
+
consumerDID: req.consumer,
|
|
118
|
+
});
|
|
115
119
|
return { decision, quote };
|
|
116
120
|
}
|
|
121
|
+
// --------------------------------------------------------------------------
|
|
122
|
+
// Long-running listener (channel-driven, multi-round)
|
|
123
|
+
// --------------------------------------------------------------------------
|
|
117
124
|
/**
|
|
118
|
-
*
|
|
119
|
-
*
|
|
125
|
+
* Subscribe to the negotiation channel and auto-respond to incoming
|
|
126
|
+
* counter-offers per `counter_strategy`. Idempotent — calling start()
|
|
127
|
+
* twice replaces the previous subscription.
|
|
128
|
+
*
|
|
129
|
+
* Returns a Subscription so the caller can stop the daemon cleanly.
|
|
130
|
+
*
|
|
131
|
+
* @throws if `negotiationChannel` or `providerDID` was not set in config.
|
|
132
|
+
*/
|
|
133
|
+
async start() {
|
|
134
|
+
if (!this.negotiationChannel) {
|
|
135
|
+
throw new Error('ProviderOrchestrator.start() requires negotiationChannel in config');
|
|
136
|
+
}
|
|
137
|
+
if (!this.providerDID) {
|
|
138
|
+
throw new Error('ProviderOrchestrator.start() requires providerDID in config');
|
|
139
|
+
}
|
|
140
|
+
// Replace any prior subscription.
|
|
141
|
+
if (this.channelSubscription) {
|
|
142
|
+
this.channelSubscription.unsubscribe();
|
|
143
|
+
}
|
|
144
|
+
const sub = this.negotiationChannel.subscribeAgent(this.providerDID, async (txId, delivered) => {
|
|
145
|
+
if (!(0, NegotiationChannel_1.isCounterOfferEnvelope)(delivered.envelope))
|
|
146
|
+
return;
|
|
147
|
+
try {
|
|
148
|
+
await this._handleIncomingCounter(txId, delivered.envelope.message);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
this.log('error', `Counter handler crashed for tx ${txId.slice(0, 12)}…: ${err instanceof Error ? err.message : String(err)}`);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
this.channelSubscription = sub;
|
|
155
|
+
this.log('info', `ProviderOrchestrator listening on channel for ${this.providerDID}`);
|
|
156
|
+
return {
|
|
157
|
+
unsubscribe: () => {
|
|
158
|
+
sub.unsubscribe();
|
|
159
|
+
this.channelSubscription = undefined;
|
|
160
|
+
this.log('info', 'ProviderOrchestrator stopped');
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Stop the active channel subscription if any. Idempotent.
|
|
166
|
+
*/
|
|
167
|
+
stop() {
|
|
168
|
+
if (this.channelSubscription) {
|
|
169
|
+
this.channelSubscription.unsubscribe();
|
|
170
|
+
this.channelSubscription = undefined;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// --------------------------------------------------------------------------
|
|
174
|
+
// Single-shot counter evaluation (tests + callers that want manual control)
|
|
175
|
+
// --------------------------------------------------------------------------
|
|
176
|
+
/**
|
|
177
|
+
* Verify + evaluate a buyer counter-offer. Returns the decision
|
|
178
|
+
* (accept / reject / requote with concession amount). Does NOT send
|
|
179
|
+
* any response — caller drives the next step. Use `start()` for
|
|
180
|
+
* autonomous operation.
|
|
181
|
+
*
|
|
182
|
+
* `lastQuoteAmountBaseUnits` — provider's most recent quote amount
|
|
183
|
+
* for this tx. On the first counter pass `counter.quoteAmount`.
|
|
184
|
+
*
|
|
185
|
+
* `requotesUsed` — defaults to 0; pass the real count for multi-round.
|
|
120
186
|
*
|
|
121
187
|
* @throws if the counter signature / band / expiry fails verify.
|
|
122
188
|
*/
|
|
123
|
-
async evaluateCounter(counter) {
|
|
189
|
+
async evaluateCounter(counter, lastQuoteAmountBaseUnits, requotesUsed = 0) {
|
|
124
190
|
await this.counterVerifier.verify(counter, this.kernelAddress);
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
|
|
191
|
+
const lastAmount = lastQuoteAmountBaseUnits ?? counter.quoteAmount;
|
|
192
|
+
const verdict = this.policyEngine.evaluateCounter(counter.counterAmount, lastAmount, requotesUsed);
|
|
193
|
+
if (verdict.decision === 'requote') {
|
|
194
|
+
return { action: 'requote', amountBaseUnits: verdict.amountBaseUnits, reason: verdict.reason };
|
|
195
|
+
}
|
|
128
196
|
return { action: verdict.decision, reason: verdict.reason };
|
|
129
197
|
}
|
|
130
198
|
/** Read-only policy accessor for UIs and tests. */
|
|
131
199
|
getPolicy() {
|
|
132
200
|
return this.policy;
|
|
133
201
|
}
|
|
202
|
+
// --------------------------------------------------------------------------
|
|
203
|
+
// Internals
|
|
204
|
+
// --------------------------------------------------------------------------
|
|
205
|
+
async _handleIncomingCounter(txId, counter) {
|
|
206
|
+
if (!this.providerDID || !this.negotiationChannel)
|
|
207
|
+
return;
|
|
208
|
+
// Look up per-tx state. If we never quoted (counter arrived without
|
|
209
|
+
// prior `quote()` call), we still process — `counter.quoteAmount`
|
|
210
|
+
// is the provider's quote per buyer's view, so we use it as baseline.
|
|
211
|
+
const state = this.txStates.get(txId) ?? {
|
|
212
|
+
lastQuote: null,
|
|
213
|
+
requotesUsed: 0,
|
|
214
|
+
consumerDID: counter.consumer,
|
|
215
|
+
};
|
|
216
|
+
const lastAmount = state.lastQuote?.quotedAmount ?? counter.quoteAmount;
|
|
217
|
+
let decision;
|
|
218
|
+
try {
|
|
219
|
+
decision = await this.evaluateCounter(counter, lastAmount, state.requotesUsed);
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
this.log('warn', `[counter] tx=${txId.slice(0, 12)}… verify failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
this.log('info', `[counter] tx=${txId.slice(0, 12)}… counter=${counter.counterAmount} → ${decision.action}: ${decision.reason}`);
|
|
226
|
+
if (decision.action === 'accept') {
|
|
227
|
+
const accept = await this.counterAcceptBuilder.build({
|
|
228
|
+
txId,
|
|
229
|
+
provider: this.providerDID,
|
|
230
|
+
consumer: counter.consumer,
|
|
231
|
+
acceptedAmount: counter.counterAmount,
|
|
232
|
+
inReplyTo: new CounterOfferBuilder_1.CounterOfferBuilder().computeHash(counter),
|
|
233
|
+
chainId: this.chainId,
|
|
234
|
+
kernelAddress: this.kernelAddress,
|
|
235
|
+
});
|
|
236
|
+
await this.negotiationChannel.post(txId, { type: 'agirails.counteraccept.v1', message: accept });
|
|
237
|
+
this.txStates.delete(txId); // terminal
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
if (decision.action === 'requote') {
|
|
241
|
+
const now = Math.floor(Date.now() / 1000);
|
|
242
|
+
const currency = this.policyEngine.policyCurrency;
|
|
243
|
+
const decimals = currency.toUpperCase() === 'USDC' ? 6 : 6;
|
|
244
|
+
// QuoteBuilder enforces `quotedAmount >= originalAmount` (AIP-2
|
|
245
|
+
// invariant: provider can't quote below buyer's offered amount).
|
|
246
|
+
// For re-quotes the buyer's original amount lives on-chain
|
|
247
|
+
// as tx.amount (set at createTransaction, immutable until
|
|
248
|
+
// acceptQuote). Fall back to counter.counterAmount if read
|
|
249
|
+
// fails (matches what buyer is willing to pay anyway).
|
|
250
|
+
let originalAmount = counter.counterAmount;
|
|
251
|
+
try {
|
|
252
|
+
const onChainTx = await this.runtime.getTransaction(txId);
|
|
253
|
+
if (onChainTx?.amount)
|
|
254
|
+
originalAmount = String(onChainTx.amount);
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
/* fall back to counter.counterAmount */
|
|
258
|
+
}
|
|
259
|
+
const newQuote = await this.quoteBuilder.build({
|
|
260
|
+
txId,
|
|
261
|
+
provider: this.providerDID,
|
|
262
|
+
consumer: counter.consumer,
|
|
263
|
+
quotedAmount: decision.amountBaseUnits,
|
|
264
|
+
originalAmount,
|
|
265
|
+
maxPrice: counter.maxPrice,
|
|
266
|
+
currency,
|
|
267
|
+
decimals,
|
|
268
|
+
expiresAt: now + this.policyEngine.quoteTtlSeconds,
|
|
269
|
+
chainId: this.chainId,
|
|
270
|
+
kernelAddress: this.kernelAddress,
|
|
271
|
+
});
|
|
272
|
+
// Re-quotes are off-chain only — kernel forbids QUOTED → QUOTED.
|
|
273
|
+
await this.negotiationChannel.post(txId, { type: 'agirails.quote.v1', message: newQuote });
|
|
274
|
+
this.txStates.set(txId, {
|
|
275
|
+
lastQuote: newQuote,
|
|
276
|
+
requotesUsed: state.requotesUsed + 1,
|
|
277
|
+
consumerDID: counter.consumer,
|
|
278
|
+
});
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
// reject — let buyer's TTL expire to CANCELLED. Drop state.
|
|
282
|
+
this.txStates.delete(txId);
|
|
283
|
+
}
|
|
134
284
|
}
|
|
135
285
|
exports.ProviderOrchestrator = ProviderOrchestrator;
|
|
136
286
|
//# sourceMappingURL=ProviderOrchestrator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderOrchestrator.js","sourceRoot":"","sources":["../../src/negotiation/ProviderOrchestrator.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"ProviderOrchestrator.js","sourceRoot":"","sources":["../../src/negotiation/ProviderOrchestrator.ts"],"names":[],"mappings":"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"}
|
|
@@ -48,6 +48,37 @@ export interface ProviderPolicy {
|
|
|
48
48
|
* Defaults to 60s if omitted.
|
|
49
49
|
*/
|
|
50
50
|
min_deadline_seconds?: number;
|
|
51
|
+
/**
|
|
52
|
+
* Multi-round counter strategy (3.5.0). Controls what the orchestrator
|
|
53
|
+
* does when a buyer's counter is below our floor:
|
|
54
|
+
*
|
|
55
|
+
* 'walk' — reject and log; buyer's TTL expires → CANCELLED. Default.
|
|
56
|
+
* 'concede' — re-quote at a price between our previous quote and
|
|
57
|
+
* our floor (governed by `concede_pct`), up to
|
|
58
|
+
* `max_requotes` times before walking.
|
|
59
|
+
*
|
|
60
|
+
* The buyer side enforces its own `rounds_per_provider` cap; provider
|
|
61
|
+
* `max_requotes` is independent and provides defense-in-depth — if a
|
|
62
|
+
* misbehaving buyer keeps countering low, we stop responding after N
|
|
63
|
+
* concessions.
|
|
64
|
+
*/
|
|
65
|
+
counter_strategy?: 'walk' | 'concede';
|
|
66
|
+
/**
|
|
67
|
+
* Concede strategy: when re-quoting, our new amount =
|
|
68
|
+
* last_quote - (last_quote - floor) * concede_pct / 100
|
|
69
|
+
*
|
|
70
|
+
* Default 30 (we move 30% of the way from last quote toward floor
|
|
71
|
+
* each round). Bounded [1, 99]; 100 would give the buyer everything
|
|
72
|
+
* (just accept), 0 would be a no-op.
|
|
73
|
+
*/
|
|
74
|
+
concede_pct?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Hard cap on re-quotes per (provider, txId). Default 2 — combined
|
|
77
|
+
* with the buyer's typical rounds_per_provider=3 this gives 1 initial
|
|
78
|
+
* quote + 2 re-quotes = 3 quote messages total per tx, matching the
|
|
79
|
+
* buyer's expected exchange depth.
|
|
80
|
+
*/
|
|
81
|
+
max_requotes?: number;
|
|
51
82
|
}
|
|
52
83
|
export type ProviderPolicyViolation = {
|
|
53
84
|
rule: 'service_not_offered';
|
|
@@ -117,15 +148,29 @@ export declare class ProviderPolicyEngine {
|
|
|
117
148
|
*/
|
|
118
149
|
evaluate(req: IncomingRequest): ProviderPolicyResult;
|
|
119
150
|
/**
|
|
120
|
-
* Decide
|
|
151
|
+
* Decide what to do with a buyer's counter-offer (3.5.0 multi-round).
|
|
152
|
+
*
|
|
153
|
+
* accept — counter ≥ floor: take the deal
|
|
154
|
+
* requote — counter < floor AND counter_strategy === 'concede' AND
|
|
155
|
+
* requotesUsed < max_requotes: send a new quote at the
|
|
156
|
+
* concession price (between last quote and floor)
|
|
157
|
+
* reject — anything else (walk strategy, or requote budget spent)
|
|
158
|
+
*
|
|
159
|
+
* `lastQuoteAmountBaseUnits` is the amount we most recently quoted to
|
|
160
|
+
* this txId — the concession baseline. On the FIRST counter it equals
|
|
161
|
+
* the counter's `quoteAmount` field (provider's original quote);
|
|
162
|
+
* on subsequent rounds it equals the orchestrator's most recent
|
|
163
|
+
* re-quote.
|
|
164
|
+
*
|
|
165
|
+
* `requotesUsed` is how many re-quotes we've already sent for this
|
|
166
|
+
* txId. Pass 0 on first call.
|
|
121
167
|
*
|
|
122
|
-
*
|
|
123
|
-
* All arithmetic is BigInt on base units — no float drift.
|
|
124
|
-
* Phase 3 will extend this with `counter-counter` strategies.
|
|
168
|
+
* All arithmetic uses BigInt on base units — no float drift.
|
|
125
169
|
*/
|
|
126
|
-
evaluateCounter(counterAmountBaseUnits: string): {
|
|
127
|
-
decision: 'accept' | 'reject';
|
|
170
|
+
evaluateCounter(counterAmountBaseUnits: string, lastQuoteAmountBaseUnits: string, requotesUsed: number): {
|
|
171
|
+
decision: 'accept' | 'reject' | 'requote';
|
|
128
172
|
reason: string;
|
|
173
|
+
amountBaseUnits?: string;
|
|
129
174
|
};
|
|
130
175
|
/** Expose ttl as seconds for callers building QuoteMessage.expiresAt. */
|
|
131
176
|
get quoteTtlSeconds(): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProviderPolicy.d.ts","sourceRoot":"","sources":["../../src/negotiation/ProviderPolicy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAC;IAEnB,OAAO,EAAE;QACP,4DAA4D;QAC5D,cAAc,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;QACnE,4DAA4D;QAC5D,WAAW,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,IAAI,EAAE,MAAM,CAAA;SAAE,CAAC;KACjE,CAAC;IAEF,8EAA8E;IAC9E,SAAS,EAAE,MAAM,CAAC;IAElB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"ProviderPolicy.d.ts","sourceRoot":"","sources":["../../src/negotiation/ProviderPolicy.ts"],"names":[],"mappings":"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"}
|