@wiro-ai/wiro-mcp 1.3.4 → 1.4.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.
- package/README.md +173 -16
- package/dist/client.d.ts +10 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +39 -20
- package/dist/client.js.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts +2 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +25 -0
- package/dist/instructions.js.map +1 -0
- package/dist/playbooks.d.ts +8 -0
- package/dist/playbooks.d.ts.map +1 -0
- package/dist/playbooks.js +665 -0
- package/dist/playbooks.js.map +1 -0
- package/dist/prompts.d.ts +4 -0
- package/dist/prompts.d.ts.map +1 -0
- package/dist/prompts.js +133 -0
- package/dist/prompts.js.map +1 -0
- package/dist/server.d.ts +9 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +9 -3
- package/dist/server.js.map +1 -1
- package/dist/tools/apply-agent-skills.d.ts +4 -0
- package/dist/tools/apply-agent-skills.d.ts.map +1 -0
- package/dist/tools/apply-agent-skills.js +545 -0
- package/dist/tools/apply-agent-skills.js.map +1 -0
- package/dist/tools/cancel-agent-message.d.ts +4 -0
- package/dist/tools/cancel-agent-message.d.ts.map +1 -0
- package/dist/tools/cancel-agent-message.js +126 -0
- package/dist/tools/cancel-agent-message.js.map +1 -0
- package/dist/tools/cancel-task.js +3 -3
- package/dist/tools/cancel-task.js.map +1 -1
- package/dist/tools/clone-workflow.d.ts +4 -0
- package/dist/tools/clone-workflow.d.ts.map +1 -0
- package/dist/tools/clone-workflow.js +83 -0
- package/dist/tools/clone-workflow.js.map +1 -0
- package/dist/tools/connect-agent-oauth.d.ts +4 -0
- package/dist/tools/connect-agent-oauth.d.ts.map +1 -0
- package/dist/tools/connect-agent-oauth.js +508 -0
- package/dist/tools/connect-agent-oauth.js.map +1 -0
- package/dist/tools/control-agent.d.ts +4 -0
- package/dist/tools/control-agent.d.ts.map +1 -0
- package/dist/tools/control-agent.js +274 -0
- package/dist/tools/control-agent.js.map +1 -0
- package/dist/tools/create-workflow.d.ts +100 -0
- package/dist/tools/create-workflow.d.ts.map +1 -0
- package/dist/tools/create-workflow.js +103 -0
- package/dist/tools/create-workflow.js.map +1 -0
- package/dist/tools/delete-agent-session.d.ts +6 -0
- package/dist/tools/delete-agent-session.d.ts.map +1 -0
- package/dist/tools/delete-agent-session.js +134 -0
- package/dist/tools/delete-agent-session.js.map +1 -0
- package/dist/tools/delete-agent.d.ts +4 -0
- package/dist/tools/delete-agent.d.ts.map +1 -0
- package/dist/tools/delete-agent.js +144 -0
- package/dist/tools/delete-agent.js.map +1 -0
- package/dist/tools/delete-workflow.d.ts +4 -0
- package/dist/tools/delete-workflow.d.ts.map +1 -0
- package/dist/tools/delete-workflow.js +77 -0
- package/dist/tools/delete-workflow.js.map +1 -0
- package/dist/tools/deploy-agent.d.ts +4 -0
- package/dist/tools/deploy-agent.d.ts.map +1 -0
- package/dist/tools/deploy-agent.js +455 -0
- package/dist/tools/deploy-agent.js.map +1 -0
- package/dist/tools/disconnect-agent-oauth.d.ts +4 -0
- package/dist/tools/disconnect-agent-oauth.d.ts.map +1 -0
- package/dist/tools/disconnect-agent-oauth.js +118 -0
- package/dist/tools/disconnect-agent-oauth.js.map +1 -0
- package/dist/tools/get-account.d.ts +4 -0
- package/dist/tools/get-account.d.ts.map +1 -0
- package/dist/tools/get-account.js +110 -0
- package/dist/tools/get-account.js.map +1 -0
- package/dist/tools/get-agent-activity.d.ts +4 -0
- package/dist/tools/get-agent-activity.d.ts.map +1 -0
- package/dist/tools/get-agent-activity.js +383 -0
- package/dist/tools/get-agent-activity.js.map +1 -0
- package/dist/tools/get-agent-conversation.d.ts +4 -0
- package/dist/tools/get-agent-conversation.d.ts.map +1 -0
- package/dist/tools/get-agent-conversation.js +224 -0
- package/dist/tools/get-agent-conversation.js.map +1 -0
- package/dist/tools/get-agent-message.d.ts +4 -0
- package/dist/tools/get-agent-message.d.ts.map +1 -0
- package/dist/tools/get-agent-message.js +68 -0
- package/dist/tools/get-agent-message.js.map +1 -0
- package/dist/tools/get-agent-template.d.ts +4 -0
- package/dist/tools/get-agent-template.d.ts.map +1 -0
- package/dist/tools/get-agent-template.js +107 -0
- package/dist/tools/get-agent-template.js.map +1 -0
- package/dist/tools/get-model-schema.d.ts +5 -1
- package/dist/tools/get-model-schema.d.ts.map +1 -1
- package/dist/tools/get-model-schema.js +136 -8
- package/dist/tools/get-model-schema.js.map +1 -1
- package/dist/tools/get-my-agent.d.ts +4 -0
- package/dist/tools/get-my-agent.d.ts.map +1 -0
- package/dist/tools/get-my-agent.js +78 -0
- package/dist/tools/get-my-agent.js.map +1 -0
- package/dist/tools/get-playbook.d.ts +3 -0
- package/dist/tools/get-playbook.d.ts.map +1 -0
- package/dist/tools/get-playbook.js +50 -0
- package/dist/tools/get-playbook.js.map +1 -0
- package/dist/tools/get-task-price.d.ts.map +1 -1
- package/dist/tools/get-task-price.js +42 -15
- package/dist/tools/get-task-price.js.map +1 -1
- package/dist/tools/get-task.d.ts.map +1 -1
- package/dist/tools/get-task.js +4 -3
- package/dist/tools/get-task.js.map +1 -1
- package/dist/tools/get-workflow.d.ts +9 -0
- package/dist/tools/get-workflow.d.ts.map +1 -0
- package/dist/tools/get-workflow.js +99 -0
- package/dist/tools/get-workflow.js.map +1 -0
- package/dist/tools/index.d.ts +62 -2
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +127 -14
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/kill-task.d.ts.map +1 -1
- package/dist/tools/kill-task.js +10 -6
- package/dist/tools/kill-task.js.map +1 -1
- package/dist/tools/list-agent-skills.d.ts +4 -0
- package/dist/tools/list-agent-skills.d.ts.map +1 -0
- package/dist/tools/list-agent-skills.js +242 -0
- package/dist/tools/list-agent-skills.js.map +1 -0
- package/dist/tools/list-agent-templates.d.ts +4 -0
- package/dist/tools/list-agent-templates.d.ts.map +1 -0
- package/dist/tools/list-agent-templates.js +110 -0
- package/dist/tools/list-agent-templates.js.map +1 -0
- package/dist/tools/list-my-agents.d.ts +4 -0
- package/dist/tools/list-my-agents.d.ts.map +1 -0
- package/dist/tools/list-my-agents.js +90 -0
- package/dist/tools/list-my-agents.js.map +1 -0
- package/dist/tools/list-studio-results.d.ts +4 -0
- package/dist/tools/list-studio-results.d.ts.map +1 -0
- package/dist/tools/list-studio-results.js +95 -0
- package/dist/tools/list-studio-results.js.map +1 -0
- package/dist/tools/list-tasks.d.ts.map +1 -1
- package/dist/tools/list-tasks.js +7 -5
- package/dist/tools/list-tasks.js.map +1 -1
- package/dist/tools/list-workflow-runs.d.ts +4 -0
- package/dist/tools/list-workflow-runs.d.ts.map +1 -0
- package/dist/tools/list-workflow-runs.js +110 -0
- package/dist/tools/list-workflow-runs.js.map +1 -0
- package/dist/tools/list-workflows.d.ts +4 -0
- package/dist/tools/list-workflows.d.ts.map +1 -0
- package/dist/tools/list-workflows.js +143 -0
- package/dist/tools/list-workflows.js.map +1 -0
- package/dist/tools/manage-agent-billing.d.ts +4 -0
- package/dist/tools/manage-agent-billing.d.ts.map +1 -0
- package/dist/tools/manage-agent-billing.js +764 -0
- package/dist/tools/manage-agent-billing.js.map +1 -0
- package/dist/tools/manage-agent-custom-skill.d.ts +4 -0
- package/dist/tools/manage-agent-custom-skill.d.ts.map +1 -0
- package/dist/tools/manage-agent-custom-skill.js +537 -0
- package/dist/tools/manage-agent-custom-skill.js.map +1 -0
- package/dist/tools/recommend-model.d.ts.map +1 -1
- package/dist/tools/recommend-model.js +6 -5
- package/dist/tools/recommend-model.js.map +1 -1
- package/dist/tools/resume-workflow-run.d.ts +4 -0
- package/dist/tools/resume-workflow-run.d.ts.map +1 -0
- package/dist/tools/resume-workflow-run.js +69 -0
- package/dist/tools/resume-workflow-run.js.map +1 -0
- package/dist/tools/run-model.d.ts.map +1 -1
- package/dist/tools/run-model.js +39 -3
- package/dist/tools/run-model.js.map +1 -1
- package/dist/tools/run-workflow.d.ts +6 -0
- package/dist/tools/run-workflow.d.ts.map +1 -0
- package/dist/tools/run-workflow.js +132 -0
- package/dist/tools/run-workflow.js.map +1 -0
- package/dist/tools/save-workflow-from-agent.d.ts +4 -0
- package/dist/tools/save-workflow-from-agent.d.ts.map +1 -0
- package/dist/tools/save-workflow-from-agent.js +283 -0
- package/dist/tools/save-workflow-from-agent.js.map +1 -0
- package/dist/tools/schemas.d.ts +62 -12
- package/dist/tools/schemas.d.ts.map +1 -1
- package/dist/tools/schemas.js +17 -3
- package/dist/tools/schemas.js.map +1 -1
- package/dist/tools/search-models.js +6 -6
- package/dist/tools/search-models.js.map +1 -1
- package/dist/tools/send-agent-message.d.ts +4 -0
- package/dist/tools/send-agent-message.d.ts.map +1 -0
- package/dist/tools/send-agent-message.js +156 -0
- package/dist/tools/send-agent-message.js.map +1 -0
- package/dist/tools/set-agent-credentials.d.ts +4 -0
- package/dist/tools/set-agent-credentials.d.ts.map +1 -0
- package/dist/tools/set-agent-credentials.js +529 -0
- package/dist/tools/set-agent-credentials.js.map +1 -0
- package/dist/tools/update-agent.d.ts +4 -0
- package/dist/tools/update-agent.d.ts.map +1 -0
- package/dist/tools/update-agent.js +178 -0
- package/dist/tools/update-agent.js.map +1 -0
- package/dist/tools/update-workflow.d.ts +201 -0
- package/dist/tools/update-workflow.d.ts.map +1 -0
- package/dist/tools/update-workflow.js +168 -0
- package/dist/tools/update-workflow.js.map +1 -0
- package/dist/tools/upload-file.d.ts.map +1 -1
- package/dist/tools/upload-file.js +4 -3
- package/dist/tools/upload-file.js.map +1 -1
- package/dist/tools/wait-for-workflow-run.d.ts +259 -0
- package/dist/tools/wait-for-workflow-run.d.ts.map +1 -0
- package/dist/tools/wait-for-workflow-run.js +190 -0
- package/dist/tools/wait-for-workflow-run.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/agent-catalog.d.ts +1256 -0
- package/dist/utils/agent-catalog.d.ts.map +1 -0
- package/dist/utils/agent-catalog.js +388 -0
- package/dist/utils/agent-catalog.js.map +1 -0
- package/dist/utils/agent-config.d.ts +441 -0
- package/dist/utils/agent-config.d.ts.map +1 -0
- package/dist/utils/agent-config.js +1667 -0
- package/dist/utils/agent-config.js.map +1 -0
- package/dist/utils/agent-message.d.ts +115 -0
- package/dist/utils/agent-message.d.ts.map +1 -0
- package/dist/utils/agent-message.js +246 -0
- package/dist/utils/agent-message.js.map +1 -0
- package/dist/utils/agent-targets.d.ts +76 -0
- package/dist/utils/agent-targets.d.ts.map +1 -0
- package/dist/utils/agent-targets.js +408 -0
- package/dist/utils/agent-targets.js.map +1 -0
- package/dist/utils/agent-turns.d.ts +354 -0
- package/dist/utils/agent-turns.d.ts.map +1 -0
- package/dist/utils/agent-turns.js +623 -0
- package/dist/utils/agent-turns.js.map +1 -0
- package/dist/utils/api.d.ts +40 -0
- package/dist/utils/api.d.ts.map +1 -0
- package/dist/utils/api.js +111 -0
- package/dist/utils/api.js.map +1 -0
- package/dist/utils/format.d.ts +38 -1
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +162 -13
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/media.d.ts +28 -0
- package/dist/utils/media.d.ts.map +1 -0
- package/dist/utils/media.js +65 -0
- package/dist/utils/media.js.map +1 -0
- package/dist/utils/poll.d.ts +39 -0
- package/dist/utils/poll.d.ts.map +1 -0
- package/dist/utils/poll.js +111 -0
- package/dist/utils/poll.js.map +1 -0
- package/dist/utils/remote-file.js +2 -2
- package/dist/utils/remote-file.js.map +1 -1
- package/dist/utils/structured.d.ts +2 -0
- package/dist/utils/structured.d.ts.map +1 -1
- package/dist/utils/structured.js +9 -6
- package/dist/utils/structured.js.map +1 -1
- package/dist/utils/studio-runs.d.ts +87 -0
- package/dist/utils/studio-runs.d.ts.map +1 -0
- package/dist/utils/studio-runs.js +167 -0
- package/dist/utils/studio-runs.js.map +1 -0
- package/dist/utils/user-agent.d.ts +1140 -0
- package/dist/utils/user-agent.d.ts.map +1 -0
- package/dist/utils/user-agent.js +802 -0
- package/dist/utils/user-agent.js.map +1 -0
- package/dist/utils/workflow-agent.d.ts +151 -0
- package/dist/utils/workflow-agent.d.ts.map +1 -0
- package/dist/utils/workflow-agent.js +292 -0
- package/dist/utils/workflow-agent.js.map +1 -0
- package/dist/utils/workflow-artifacts.d.ts +160 -0
- package/dist/utils/workflow-artifacts.d.ts.map +1 -0
- package/dist/utils/workflow-artifacts.js +801 -0
- package/dist/utils/workflow-artifacts.js.map +1 -0
- package/dist/utils/workflow-save.d.ts +123 -0
- package/dist/utils/workflow-save.d.ts.map +1 -0
- package/dist/utils/workflow-save.js +734 -0
- package/dist/utils/workflow-save.js.map +1 -0
- package/dist/utils/workflow.d.ts +559 -0
- package/dist/utils/workflow.d.ts.map +1 -0
- package/dist/utils/workflow.js +613 -0
- package/dist/utils/workflow.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,764 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { ApiResultError, asString, errorResult, errorText, isPlainObject, requireApiSuccess, toNumber, unixToIso, } from '../utils/api.js';
|
|
4
|
+
import { workspaceHint } from '../utils/agent-config.js';
|
|
5
|
+
import { DEFAULT_WAIT_SECONDS } from '../utils/poll.js';
|
|
6
|
+
import { AMOUNT_TOLERANCE_USD, agentDashboardUrl, agentSummarySchema, asTier, chargeRoute, formatAgentSummaryLines, formatCredits, formatUsd, outcomeUnknown, readAgentDetail, roundUsd, sameAmount, toAgentDetail, toAgentSummary, } from '../utils/user-agent.js';
|
|
7
|
+
import { nextActionSchema } from './schemas.js';
|
|
8
|
+
const ACTIONS = ['subscribe', 'upgrade_to_pro', 'cancel', 'renew', 'buy_credits'];
|
|
9
|
+
const quoteSchema = z.object({
|
|
10
|
+
chargeNowUsd: z.number().nullable(),
|
|
11
|
+
estimated: z.boolean(),
|
|
12
|
+
maxChargeUsd: z.number().optional(),
|
|
13
|
+
recurringUsd: z.number().nullable().optional(),
|
|
14
|
+
monthlyCredits: z.number().nullable().optional(),
|
|
15
|
+
creditsAdded: z.number().optional(),
|
|
16
|
+
paidFrom: z.enum(['personal_wallet', 'team_wallet', 'card']),
|
|
17
|
+
tier: z.enum(['starter', 'pro']).optional(),
|
|
18
|
+
pack: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
const resultSchema = z.object({
|
|
21
|
+
chargedUsd: z.number().nullable().optional(),
|
|
22
|
+
monthlyPriceUsd: z.number().optional(),
|
|
23
|
+
monthlyCredits: z.number().optional(),
|
|
24
|
+
creditsAdded: z.number().optional(),
|
|
25
|
+
tier: z.string().optional(),
|
|
26
|
+
previousTier: z.string().optional(),
|
|
27
|
+
cancelsAt: z.string().optional(),
|
|
28
|
+
renewal: z.enum(['undo_cancel', 'renewed']).optional(),
|
|
29
|
+
});
|
|
30
|
+
const CHARGE_REASON = 'Ask the user to approve this charge first.';
|
|
31
|
+
function newRequestId() {
|
|
32
|
+
return randomUUID().replace(/-/g, '');
|
|
33
|
+
}
|
|
34
|
+
function billingCall(guid, action, extra = {}) {
|
|
35
|
+
return {
|
|
36
|
+
tool: 'manage_agent_billing',
|
|
37
|
+
arguments: { agent_guid: guid, action, ...extra },
|
|
38
|
+
reason: 'This returns the price first; nothing is charged without confirm: true.',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function waitAction(guid, reason) {
|
|
42
|
+
return {
|
|
43
|
+
tool: 'get_my_agent',
|
|
44
|
+
arguments: { agent_guid: guid, wait_seconds: DEFAULT_WAIT_SECONDS },
|
|
45
|
+
reason,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function tierName(tier) {
|
|
49
|
+
return tier === 'pro' ? 'Pro' : 'Starter';
|
|
50
|
+
}
|
|
51
|
+
/** The amount a confirmed call charges now, or null when it charges nothing. */
|
|
52
|
+
function chargeOf(quote) {
|
|
53
|
+
const amount = quote?.chargeNowUsd ?? null;
|
|
54
|
+
return amount !== null && amount > 0 ? amount : null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The amount the user approves with confirm_charge_usd, or null when the
|
|
58
|
+
* call charges nothing. Wiro prorates an upgrade over the real length of
|
|
59
|
+
* the billing period, which the 30-day estimate does not know, so an upgrade
|
|
60
|
+
* is approved at its maximum: the full price difference, which the prorated
|
|
61
|
+
* charge never exceeds.
|
|
62
|
+
*/
|
|
63
|
+
function approvalAmountOf(action, quote) {
|
|
64
|
+
if (action !== 'upgrade_to_pro')
|
|
65
|
+
return chargeOf(quote);
|
|
66
|
+
const max = quote?.maxChargeUsd ?? null;
|
|
67
|
+
return max !== null && max > 0 ? max : null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Why a confirmed call may not charge yet: the approved amount is missing,
|
|
71
|
+
* or the fresh quote no longer matches it. For an upgrade the approved
|
|
72
|
+
* amount is a ceiling.
|
|
73
|
+
*/
|
|
74
|
+
function approvalProblem(action, quote, approved) {
|
|
75
|
+
const amount = approvalAmountOf(action, quote);
|
|
76
|
+
if (amount === null)
|
|
77
|
+
return null;
|
|
78
|
+
if (approved === undefined)
|
|
79
|
+
return 'missing';
|
|
80
|
+
const fits = action === 'upgrade_to_pro'
|
|
81
|
+
? amount <= approved + AMOUNT_TOLERANCE_USD
|
|
82
|
+
: sameAmount(amount, approved);
|
|
83
|
+
return fits ? null : 'changed';
|
|
84
|
+
}
|
|
85
|
+
/** The first lines of a new quote when the approved amount no longer fits. */
|
|
86
|
+
function changedLead(action, amount, approved, callerRequestId) {
|
|
87
|
+
if (action === 'upgrade_to_pro') {
|
|
88
|
+
return `The most this upgrade can charge changed since the quote: it is now ${formatUsd(amount)}, and the user approved at most ${formatUsd(approved)}. Nothing was charged. Show the new amount to the user and ask again.`;
|
|
89
|
+
}
|
|
90
|
+
const changed = `The amount changed since the quote: it is now ${formatUsd(amount)}, and the user approved ${formatUsd(approved)}.`;
|
|
91
|
+
if (action === 'buy_credits' && callerRequestId) {
|
|
92
|
+
// Wiro binds a purchase request id to the price of its first attempt.
|
|
93
|
+
return `${changed} Nothing was charged by this call. Show the new amount to the user and ask again. `
|
|
94
|
+
+ `If an earlier call with request_id "${callerRequestId}" said the wallet was charged but the purchase is still pending, that purchase can only be finished at its original price, and Wiro refuses this request id at the new one. Do not approve the new amount for it; ask Wiro support to finish it.`;
|
|
95
|
+
}
|
|
96
|
+
return `${changed} Nothing was charged. Show the new amount to the user and ask again.`;
|
|
97
|
+
}
|
|
98
|
+
function planFor(client, action, agent, raw, input) {
|
|
99
|
+
const guid = agent.guid;
|
|
100
|
+
const active = raw?.status === 'active';
|
|
101
|
+
const prepaid = raw?.provider === 'prepaid';
|
|
102
|
+
const cancelled = raw?.pendingdowngrade === 'cancel';
|
|
103
|
+
const route = chargeRoute(raw);
|
|
104
|
+
const paidFrom = agent.workspace === 'team' ? 'team_wallet' : 'personal_wallet';
|
|
105
|
+
const wallet = agent.workspace === 'team' ? 'the team wallet' : 'the personal wallet';
|
|
106
|
+
const periodEnd = agent.subscription?.periodEndsAt ?? 'the end of the current period';
|
|
107
|
+
const cardPlan = (what) => `This plan is billed by card, and card payments are made in the Wiro dashboard. Nothing was charged here. To ${what}, open ${agent.dashboardUrl}`;
|
|
108
|
+
switch (action) {
|
|
109
|
+
case 'subscribe': {
|
|
110
|
+
if (active) {
|
|
111
|
+
return {
|
|
112
|
+
outcome: 'refused',
|
|
113
|
+
message: 'This agent already has an active plan. Use renew to undo a cancellation, upgrade_to_pro to move to Pro, or buy_credits for more credits.',
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const tier = input.tier ?? asTier(agent.tier) ?? 'starter';
|
|
117
|
+
const price = agent.pricing?.[tier];
|
|
118
|
+
if (!price)
|
|
119
|
+
throw new Error('This agent cannot be priced right now, so it cannot be subscribed. Try again later.');
|
|
120
|
+
const missing = agent.missingCredentials;
|
|
121
|
+
return {
|
|
122
|
+
outcome: 'quote',
|
|
123
|
+
quote: {
|
|
124
|
+
chargeNowUsd: price.priceUsd,
|
|
125
|
+
estimated: false,
|
|
126
|
+
recurringUsd: price.priceUsd,
|
|
127
|
+
monthlyCredits: price.monthlyCredits,
|
|
128
|
+
paidFrom,
|
|
129
|
+
tier,
|
|
130
|
+
},
|
|
131
|
+
message: `Subscribe on ${tierName(tier)}: ${formatUsd(price.priceUsd)} now from ${wallet}, then every 30 days until the plan is cancelled, with ${formatCredits(price.monthlyCredits)} credits per month. `
|
|
132
|
+
+ 'The agent is queued to start right after the payment.'
|
|
133
|
+
+ (missing.length > 0
|
|
134
|
+
? ` Missing credentials (${missing.map(item => item.title).join(', ')}) can be added afterwards; until then the skills that need them fail.`
|
|
135
|
+
: ''),
|
|
136
|
+
perform: async (signal) => {
|
|
137
|
+
const body = requireApiSuccess(await client.post('/UserAgent/CreateSubscriptionCheckout', {
|
|
138
|
+
useragentguid: guid,
|
|
139
|
+
useprepaid: true,
|
|
140
|
+
tier,
|
|
141
|
+
}, signal), 'The plan could not be started.');
|
|
142
|
+
const charged = toNumber(body.monthlypriceusd) ?? price.priceUsd;
|
|
143
|
+
const credits = toNumber(body.monthlycredits) ?? price.monthlyCredits;
|
|
144
|
+
return {
|
|
145
|
+
outcome: 'done',
|
|
146
|
+
message: `Subscribed on ${tierName(tier)} and charged ${formatUsd(charged)} from ${wallet}. The plan renews every 30 days until it is cancelled. The agent is starting.`,
|
|
147
|
+
result: { chargedUsd: charged, monthlyPriceUsd: charged, monthlyCredits: credits, tier },
|
|
148
|
+
nextAction: waitAction(guid, 'Wait until the agent is running.'),
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
case 'renew': {
|
|
154
|
+
if (!raw) {
|
|
155
|
+
return {
|
|
156
|
+
outcome: 'refused',
|
|
157
|
+
message: 'This agent has never had a plan. Use action subscribe.',
|
|
158
|
+
nextAction: billingCall(guid, 'subscribe'),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
if (active) {
|
|
162
|
+
if (!prepaid) {
|
|
163
|
+
// A card plan can be set to stop at its period end, by its payer or when the
|
|
164
|
+
// payer's account is deleted.
|
|
165
|
+
return {
|
|
166
|
+
outcome: 'refused',
|
|
167
|
+
message: agent.subscription?.cancelAtPeriodEnd
|
|
168
|
+
? `This plan is billed by card and is set to stop renewing after ${periodEnd}. Card plans are managed `
|
|
169
|
+
+ `from the dashboard, where it can be kept: ${agent.dashboardUrl}`
|
|
170
|
+
: `This plan is billed by card and renews by itself. It is managed from the dashboard: ${agent.dashboardUrl}`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (!cancelled) {
|
|
174
|
+
return { outcome: 'not_needed', message: 'The plan is active and renews by itself. Nothing to do.' };
|
|
175
|
+
}
|
|
176
|
+
const amount = agent.subscription?.amountUsd ?? agent.monthlyPriceUsd;
|
|
177
|
+
return {
|
|
178
|
+
outcome: 'quote',
|
|
179
|
+
quote: { chargeNowUsd: 0, estimated: false, recurringUsd: amount, monthlyCredits: agent.credits.monthly, paidFrom },
|
|
180
|
+
message: `Undo the cancellation: nothing is charged now. The plan then renews from ${wallet} for ${formatUsd(amount)} after ${periodEnd} and every 30 days after that until it is cancelled.`,
|
|
181
|
+
confirmReason: 'Ask the user to confirm first: the plan will charge the wallet again at each renewal.',
|
|
182
|
+
perform: async (signal) => {
|
|
183
|
+
requireApiSuccess(await client.post('/UserAgent/RenewSubscription', { guid }, signal), 'The cancellation could not be undone.');
|
|
184
|
+
return {
|
|
185
|
+
outcome: 'done',
|
|
186
|
+
message: 'The cancellation was undone. The plan keeps renewing every 30 days.',
|
|
187
|
+
result: { chargedUsd: 0, renewal: 'undo_cancel' },
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
if (raw.status !== 'expired') {
|
|
193
|
+
return {
|
|
194
|
+
outcome: 'refused',
|
|
195
|
+
message: `The last plan is ${String(raw.status)}. Use action subscribe to start a new one.`,
|
|
196
|
+
nextAction: billingCall(guid, 'subscribe'),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
if (!prepaid) {
|
|
200
|
+
return {
|
|
201
|
+
outcome: 'refused',
|
|
202
|
+
message: 'The expired plan was billed by card. Use action subscribe to start a new plan paid from the wallet.',
|
|
203
|
+
nextAction: billingCall(guid, 'subscribe'),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
const price = agent.monthlyPriceUsd ?? toNumber(raw.amount);
|
|
207
|
+
if (!price || price <= 0) {
|
|
208
|
+
return {
|
|
209
|
+
outcome: 'refused',
|
|
210
|
+
message: 'The renewal price of this plan is unknown. Use action subscribe to start a new plan.',
|
|
211
|
+
nextAction: billingCall(guid, 'subscribe'),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
const tier = asTier(agent.tier);
|
|
215
|
+
return {
|
|
216
|
+
outcome: 'quote',
|
|
217
|
+
quote: {
|
|
218
|
+
chargeNowUsd: price,
|
|
219
|
+
estimated: false,
|
|
220
|
+
recurringUsd: price,
|
|
221
|
+
monthlyCredits: tier ? agent.pricing?.[tier].monthlyCredits ?? null : null,
|
|
222
|
+
paidFrom,
|
|
223
|
+
...(tier ? { tier } : {}),
|
|
224
|
+
},
|
|
225
|
+
message: `Renew the expired plan: ${formatUsd(price)} now from ${wallet}, then every 30 days until it is cancelled. A stopped or failed agent is queued to start again.`,
|
|
226
|
+
perform: async (signal) => {
|
|
227
|
+
const body = requireApiSuccess(await client.post('/UserAgent/RenewSubscription', { guid }, signal), 'The plan could not be renewed.');
|
|
228
|
+
const charged = toNumber(body.amount) ?? price;
|
|
229
|
+
const credits = toNumber(body.monthlycredits);
|
|
230
|
+
return {
|
|
231
|
+
outcome: 'done',
|
|
232
|
+
message: `Renewed and charged ${formatUsd(charged)} from ${wallet}. The plan renews every 30 days until it is cancelled.`,
|
|
233
|
+
result: {
|
|
234
|
+
chargedUsd: charged,
|
|
235
|
+
monthlyPriceUsd: charged,
|
|
236
|
+
...(credits !== null ? { monthlyCredits: credits } : {}),
|
|
237
|
+
renewal: 'renewed',
|
|
238
|
+
},
|
|
239
|
+
nextAction: waitAction(guid, 'Wait until the agent is running again.'),
|
|
240
|
+
};
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
case 'cancel': {
|
|
245
|
+
if (!active)
|
|
246
|
+
return { outcome: 'not_needed', message: 'There is no active plan to cancel.' };
|
|
247
|
+
const performCancel = async (signal) => {
|
|
248
|
+
const body = requireApiSuccess(await client.post('/UserAgent/CancelSubscription', { guid }, signal), 'The plan could not be cancelled.');
|
|
249
|
+
const cancelsAt = unixToIso(body.cancelsAt);
|
|
250
|
+
return {
|
|
251
|
+
outcome: 'done',
|
|
252
|
+
message: `The plan is cancelled and stops renewing${cancelsAt ? ` after ${cancelsAt}` : ''}. Nothing was refunded.`,
|
|
253
|
+
result: { chargedUsd: 0, ...(cancelsAt ? { cancelsAt } : {}) },
|
|
254
|
+
};
|
|
255
|
+
};
|
|
256
|
+
if (!prepaid) {
|
|
257
|
+
if (agent.workspace === 'personal') {
|
|
258
|
+
return {
|
|
259
|
+
outcome: 'refused',
|
|
260
|
+
message: `This plan is billed by card. Card plans are cancelled from the dashboard: ${agent.dashboardUrl}`,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
if (raw?.nextprovider === 'prepaid') {
|
|
264
|
+
return {
|
|
265
|
+
outcome: 'refused',
|
|
266
|
+
message: 'A switch to wallet billing is already scheduled for this plan, so it cannot be cancelled here.',
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
if (agent.subscription?.cancelAtPeriodEnd) {
|
|
270
|
+
return { outcome: 'not_needed', message: `The plan is already set to end after ${periodEnd}.` };
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
outcome: 'quote',
|
|
274
|
+
quote: { chargeNowUsd: 0, estimated: false, paidFrom: 'card' },
|
|
275
|
+
message: `Cancel the team's card plan at the end of the current period (${periodEnd}). Nothing is refunded.`,
|
|
276
|
+
confirmReason: 'Ask the user to confirm the cancellation first.',
|
|
277
|
+
perform: performCancel,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
if (cancelled) {
|
|
281
|
+
return { outcome: 'not_needed', message: `The plan is already cancelled and ends after ${periodEnd}.` };
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
outcome: 'quote',
|
|
285
|
+
quote: { chargeNowUsd: 0, estimated: false, paidFrom },
|
|
286
|
+
message: `Cancel the plan: nothing is refunded and it stops renewing. The agent keeps its monthly credits until about a day after ${periodEnd}; then the monthly credits end and the agent stops unless it has extra credits left. Action renew undoes the cancellation before then.`,
|
|
287
|
+
confirmReason: 'Ask the user to confirm the cancellation first.',
|
|
288
|
+
perform: performCancel,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
case 'upgrade_to_pro': {
|
|
292
|
+
if (!active) {
|
|
293
|
+
return {
|
|
294
|
+
outcome: 'refused',
|
|
295
|
+
message: 'Upgrading needs an active plan. Use action subscribe with tier pro instead.',
|
|
296
|
+
nextAction: billingCall(guid, 'subscribe', { tier: 'pro' }),
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
if (agent.tier === 'pro')
|
|
300
|
+
return { outcome: 'not_needed', message: 'The agent is already on Pro.' };
|
|
301
|
+
// No checkout pages are opened for card plans: the user pays them in the dashboard.
|
|
302
|
+
if (route === 'card')
|
|
303
|
+
return { outcome: 'refused', message: cardPlan('upgrade it to Pro') };
|
|
304
|
+
const pro = agent.pricing?.pro;
|
|
305
|
+
if (!pro)
|
|
306
|
+
throw new Error('The Pro price of this agent is not available right now. Try again later.');
|
|
307
|
+
const ending = `The plan is cancelled, so it ends about a day after ${periodEnd} and does not renew at the Pro price; this charge covers only the days left. To keep the plan, undo the cancellation first with action renew.`;
|
|
308
|
+
const current = agent.monthlyPriceUsd ?? agent.subscription?.amountUsd ?? 0;
|
|
309
|
+
const periodEndSeconds = toNumber(raw?.currentperiodend);
|
|
310
|
+
const daysLeft = periodEndSeconds !== null
|
|
311
|
+
? Math.max(0, Math.round((periodEndSeconds - Date.now() / 1000) / 86_400))
|
|
312
|
+
: 30;
|
|
313
|
+
const maxCharge = roundUsd(Math.max(0, pro.priceUsd - current));
|
|
314
|
+
const estimate = roundUsd(Math.max(0, ((pro.priceUsd - current) / 30) * daysLeft));
|
|
315
|
+
return {
|
|
316
|
+
outcome: 'quote',
|
|
317
|
+
quote: {
|
|
318
|
+
chargeNowUsd: estimate,
|
|
319
|
+
estimated: true,
|
|
320
|
+
maxChargeUsd: maxCharge,
|
|
321
|
+
recurringUsd: cancelled ? null : pro.priceUsd,
|
|
322
|
+
monthlyCredits: pro.monthlyCredits,
|
|
323
|
+
paidFrom,
|
|
324
|
+
tier: 'pro',
|
|
325
|
+
},
|
|
326
|
+
message: `Upgrade to Pro: about ${formatUsd(estimate)} now from ${wallet} for the ${daysLeft} days left in this period. `
|
|
327
|
+
+ `Wiro works out the exact amount from the real length of the billing period, and it is never more than ${formatUsd(maxCharge)}, so that is the amount to approve. `
|
|
328
|
+
+ (cancelled ? `${ending} ` : `From the next renewal the plan costs ${formatUsd(pro.priceUsd)} every 30 days. `)
|
|
329
|
+
+ `The monthly pool rises to ${formatCredits(pro.monthlyCredits)} credits at once and the agent restarts. To go back to Starter later, cancel, let the plan end, then subscribe to Starter.`,
|
|
330
|
+
perform: async (signal) => {
|
|
331
|
+
const body = requireApiSuccess(await client.post('/UserAgent/UpgradeTier', { guid, targetTier: 'pro' }, signal), 'The plan could not be upgraded.');
|
|
332
|
+
if (asString(body.url)) {
|
|
333
|
+
// A checkout link means the plan is paid by card. The link is a payment page for the owner's card, so it is not passed on.
|
|
334
|
+
return { outcome: 'refused', message: cardPlan('upgrade it to Pro') };
|
|
335
|
+
}
|
|
336
|
+
const charged = toNumber(body.proratedCharge);
|
|
337
|
+
const newPrice = toNumber(body.newPriceUsd) ?? pro.priceUsd;
|
|
338
|
+
const newCredits = toNumber(body.newMonthlyCredits) ?? pro.monthlyCredits;
|
|
339
|
+
const previousTier = asString(body.previousTier);
|
|
340
|
+
return {
|
|
341
|
+
outcome: 'done',
|
|
342
|
+
message: `Upgraded to Pro and charged ${formatUsd(charged ?? 0)} from ${wallet}. `
|
|
343
|
+
+ (cancelled
|
|
344
|
+
? `The plan is still cancelled and ends about a day after ${periodEnd}; action renew undoes the cancellation. `
|
|
345
|
+
: `The plan now costs ${formatUsd(newPrice)} every 30 days. `)
|
|
346
|
+
+ `The monthly pool is ${formatCredits(newCredits)} credits. The agent restarts.`,
|
|
347
|
+
result: {
|
|
348
|
+
chargedUsd: charged,
|
|
349
|
+
monthlyPriceUsd: newPrice,
|
|
350
|
+
monthlyCredits: newCredits,
|
|
351
|
+
tier: 'pro',
|
|
352
|
+
...(previousTier ? { previousTier } : {}),
|
|
353
|
+
},
|
|
354
|
+
nextAction: waitAction(guid, 'Wait until the agent is running again.'),
|
|
355
|
+
};
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
case 'buy_credits': {
|
|
360
|
+
if (!active) {
|
|
361
|
+
return { outcome: 'refused', message: 'Extra credits need an active plan. Use action subscribe or renew first.' };
|
|
362
|
+
}
|
|
363
|
+
if (agent.tier !== 'pro') {
|
|
364
|
+
return {
|
|
365
|
+
outcome: 'refused',
|
|
366
|
+
message: 'Extra credit packs are only available on Pro. Upgrade first with action upgrade_to_pro.',
|
|
367
|
+
nextAction: billingCall(guid, 'upgrade_to_pro'),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
if (route === 'card')
|
|
371
|
+
return { outcome: 'refused', message: cardPlan('buy the pack') };
|
|
372
|
+
const packs = agent.extraCreditPacks;
|
|
373
|
+
const packList = packs.map(item => `${item.pack} (${formatCredits(item.credits)} credits for ${formatUsd(item.priceUsd)})`).join(', ');
|
|
374
|
+
if (!input.pack) {
|
|
375
|
+
return {
|
|
376
|
+
outcome: 'refused',
|
|
377
|
+
message: packs.length > 0 ? `Choose a pack: ${packList}.` : 'No extra credit packs are available for this agent yet.',
|
|
378
|
+
...(packs[0] ? { nextAction: billingCall(guid, 'buy_credits', { pack: packs[0].pack }) } : {}),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
const chosen = packs.find(item => item.pack === input.pack);
|
|
382
|
+
if (!chosen) {
|
|
383
|
+
return {
|
|
384
|
+
outcome: 'refused',
|
|
385
|
+
message: `The ${input.pack} pack is not available for this agent.${packList ? ` Available: ${packList}.` : ''}`,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
return {
|
|
389
|
+
outcome: 'quote',
|
|
390
|
+
quote: { chargeNowUsd: chosen.priceUsd, estimated: false, creditsAdded: chosen.credits, paidFrom, pack: chosen.pack },
|
|
391
|
+
message: `Buy the ${chosen.pack} pack: ${formatUsd(chosen.priceUsd)} now from ${wallet} for ${formatCredits(chosen.credits)} extra credits, added at once. Extra credits are used after the monthly credits and do not reset each month. The agent restarts to pick them up. This is a one-time charge.`,
|
|
392
|
+
perform: async (signal) => {
|
|
393
|
+
const body = requireApiSuccess(await client.post('/UserAgent/CreateExtraCreditCheckout', {
|
|
394
|
+
useragentguid: guid,
|
|
395
|
+
pack: chosen.pack,
|
|
396
|
+
requestid: input.requestId,
|
|
397
|
+
}, signal), 'The credit pack could not be bought.');
|
|
398
|
+
if (asString(body.url))
|
|
399
|
+
return { outcome: 'refused', message: cardPlan('buy the pack') };
|
|
400
|
+
return {
|
|
401
|
+
outcome: 'done',
|
|
402
|
+
message: `Bought the ${chosen.pack} pack: ${formatCredits(chosen.credits)} credits for ${formatUsd(chosen.priceUsd)} from ${wallet}. The credits are added and the agent restarts to pick them up.`,
|
|
403
|
+
result: { chargedUsd: chosen.priceUsd, creditsAdded: chosen.credits },
|
|
404
|
+
nextAction: waitAction(guid, 'Wait until the agent is running again.'),
|
|
405
|
+
};
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
/** The arguments that repeat a quoted action with the user's approval. */
|
|
412
|
+
function confirmArguments(guid, action, quote, requestId) {
|
|
413
|
+
const amount = approvalAmountOf(action, quote);
|
|
414
|
+
return {
|
|
415
|
+
agent_guid: guid,
|
|
416
|
+
action,
|
|
417
|
+
...(action === 'subscribe' && quote?.tier ? { tier: quote.tier } : {}),
|
|
418
|
+
...(quote?.pack ? { pack: quote.pack } : {}),
|
|
419
|
+
...(requestId ? { request_id: requestId } : {}),
|
|
420
|
+
...(amount !== null ? { confirm_charge_usd: amount } : {}),
|
|
421
|
+
confirm: true,
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
function retryPurchaseText(guid, quote, requestId) {
|
|
425
|
+
const amount = chargeOf(quote);
|
|
426
|
+
return `To retry, call manage_agent_billing again with agent_guid "${guid}", action buy_credits, pack "${quote?.pack ?? ''}", request_id "${requestId}"${amount !== null ? `, confirm_charge_usd ${amount}` : ''} and confirm: true. That request id never charges twice, and it finishes a purchase that was charged but not completed. Do not get a new quote for this purchase: a new request id is a new purchase.`
|
|
427
|
+
+ (amount !== null ? ` This works only while the pack still costs ${formatUsd(amount)}; Wiro refuses the id at any other price, and then Wiro support has to finish the purchase.` : '');
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Whether the agent's packs show an active one of this size bought since
|
|
431
|
+
* the request was sent, or null when they cannot be read. A lot is dated
|
|
432
|
+
* when the purchase starts, before the wallet is charged; the margin covers
|
|
433
|
+
* a local clock that differs from Wiro's.
|
|
434
|
+
*/
|
|
435
|
+
async function freshPackShows(client, guid, credits, sentAtMs, signal) {
|
|
436
|
+
try {
|
|
437
|
+
const ledger = requireApiSuccess(await client.post('/UserAgent/TransactionList', { useragentguid: guid, limit: 1, start: 0 }, signal), 'The credit history could not be read.');
|
|
438
|
+
const summary = isPlainObject(ledger.summary) ? ledger.summary : {};
|
|
439
|
+
const lots = Array.isArray(summary.extrapacks) ? summary.extrapacks.filter(isPlainObject) : [];
|
|
440
|
+
const since = sentAtMs / 1000 - 900;
|
|
441
|
+
return lots.some(lot => lot.status === 'active'
|
|
442
|
+
&& toNumber(lot.credits) === credits
|
|
443
|
+
&& (toNumber(lot.purchasedat) ?? 0) >= since);
|
|
444
|
+
}
|
|
445
|
+
catch {
|
|
446
|
+
return null;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* After a charging request whose outcome is open, reads the agent again and
|
|
451
|
+
* says what it shows: the charge may have gone through, and the agent's
|
|
452
|
+
* detail is where its result shows.
|
|
453
|
+
*/
|
|
454
|
+
async function observeOpenOutcome(client, action, before, quote, requestId, sentAtMs, signal) {
|
|
455
|
+
let row;
|
|
456
|
+
try {
|
|
457
|
+
row = await readAgentDetail(client, before.guid, signal);
|
|
458
|
+
}
|
|
459
|
+
catch {
|
|
460
|
+
return { happened: false, text: 'The agent could not be read afterwards to check it.' };
|
|
461
|
+
}
|
|
462
|
+
const after = toAgentDetail(row);
|
|
463
|
+
const charged = chargeOf(quote);
|
|
464
|
+
const restart = waitAction(before.guid, 'Wait until the agent is running again.');
|
|
465
|
+
switch (action) {
|
|
466
|
+
case 'subscribe':
|
|
467
|
+
return after.subscription?.status === 'active'
|
|
468
|
+
? {
|
|
469
|
+
happened: true,
|
|
470
|
+
row,
|
|
471
|
+
text: 'The agent now has an active plan, so the payment went through. Do not subscribe again.',
|
|
472
|
+
result: { chargedUsd: charged, ...(quote?.tier ? { tier: quote.tier } : {}) },
|
|
473
|
+
nextAction: waitAction(before.guid, 'Wait until the agent is running.'),
|
|
474
|
+
}
|
|
475
|
+
: { happened: false, text: 'The agent shows no active plan yet. Check get_my_agent again in a minute before trying again.' };
|
|
476
|
+
case 'renew':
|
|
477
|
+
return after.subscription?.status === 'active'
|
|
478
|
+
? {
|
|
479
|
+
happened: true,
|
|
480
|
+
row,
|
|
481
|
+
text: 'The plan is active again, so the renewal went through. Do not renew it again.',
|
|
482
|
+
result: { chargedUsd: charged, renewal: 'renewed' },
|
|
483
|
+
nextAction: restart,
|
|
484
|
+
}
|
|
485
|
+
: { happened: false, text: 'The plan still shows as expired, but the wallet may have been charged anyway. Compare the wallet balance with get_account before renewing again.' };
|
|
486
|
+
case 'upgrade_to_pro': {
|
|
487
|
+
if (after.tier === 'pro') {
|
|
488
|
+
return {
|
|
489
|
+
happened: true,
|
|
490
|
+
row,
|
|
491
|
+
text: `The agent is on Pro now, so the upgrade went through. Do not upgrade again. The prorated charge is not shown, and it is at most ${formatUsd(quote?.maxChargeUsd)}.`,
|
|
492
|
+
result: { chargedUsd: null, tier: 'pro' },
|
|
493
|
+
nextAction: restart,
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
const proPrice = before.pricing?.pro.priceUsd;
|
|
497
|
+
const priced = proPrice !== undefined && [after.monthlyPriceUsd, after.subscription?.amountUsd ?? null]
|
|
498
|
+
.some(value => value !== null && sameAmount(value, proPrice));
|
|
499
|
+
return {
|
|
500
|
+
happened: false,
|
|
501
|
+
text: priced
|
|
502
|
+
? 'The plan already shows the Pro price but not the Pro tier, so the upgrade was charged and did not finish. Do not upgrade again; ask Wiro support to finish it.'
|
|
503
|
+
: 'The agent still shows Starter, but the wallet may have been charged anyway. Compare the wallet balance with get_account before trying again.',
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
case 'buy_credits': {
|
|
507
|
+
const credits = quote?.creditsAdded ?? 0;
|
|
508
|
+
const rose = after.credits.extra - before.credits.extra;
|
|
509
|
+
const bought = { chargedUsd: charged, creditsAdded: credits };
|
|
510
|
+
if (credits > 0 && rose >= credits / 2) {
|
|
511
|
+
return {
|
|
512
|
+
happened: true,
|
|
513
|
+
row,
|
|
514
|
+
text: `The agent's extra credits rose by ${formatCredits(rose)}, so the purchase went through. Do not buy it again.`,
|
|
515
|
+
result: bought,
|
|
516
|
+
nextAction: restart,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
const fresh = await freshPackShows(client, before.guid, credits, sentAtMs, signal);
|
|
520
|
+
if (fresh) {
|
|
521
|
+
// The pack is paid for, but its credits are counted only when the
|
|
522
|
+
// purchase finishes, which the same request id does without a charge.
|
|
523
|
+
return {
|
|
524
|
+
happened: true,
|
|
525
|
+
row,
|
|
526
|
+
text: 'A new pack of that size shows on the agent, so the purchase went through and the wallet was charged. Do not buy it again. '
|
|
527
|
+
+ `Its credits may not be counted yet. If get_my_agent still does not show them in a few minutes, repeating this purchase with the same request_id "${requestId ?? ''}" and confirm_charge_usd ${charged ?? ''} finishes it without a second charge.`,
|
|
528
|
+
result: bought,
|
|
529
|
+
nextAction: waitAction(before.guid, 'Check that the pack\'s credits show on the agent.'),
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
happened: false,
|
|
534
|
+
text: fresh === false ? 'No new pack shows on the agent yet.' : 'The agent\'s packs could not be read to check it.',
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
default:
|
|
538
|
+
return { happened: false, text: '' };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function annotateKnownRefusal(error) {
|
|
542
|
+
const text = error.message;
|
|
543
|
+
if (/insufficient wallet balance/i.test(text)) {
|
|
544
|
+
return new Error(`${text}\n\nNothing was charged. Funds are added at https://wiro.ai/panel/wallet/deposit/credit-card (for a team project, select that team in the dashboard first); once the user has added them and says yes, try again. get_account shows the balance.`);
|
|
545
|
+
}
|
|
546
|
+
if (/purchase request id was already used/i.test(text)) {
|
|
547
|
+
return new Error(`${text}\n\nNothing was charged now. This request id belongs to an earlier purchase of another pack or at another price. For a new purchase, get a new quote without request_id. If an earlier call with this id said the wallet was charged but the purchase is still pending, do not buy again: that purchase can only be finished at its original price, so ask Wiro support to finish it.`);
|
|
548
|
+
}
|
|
549
|
+
const hint = workspaceHint(text);
|
|
550
|
+
if (hint)
|
|
551
|
+
return new Error(`${text}\n\n${hint}`);
|
|
552
|
+
return error;
|
|
553
|
+
}
|
|
554
|
+
export function registerManageAgentBilling(server, client) {
|
|
555
|
+
server.registerTool('manage_agent_billing', {
|
|
556
|
+
title: 'Manage a Wiro agent\'s plan and credits',
|
|
557
|
+
description: 'Pay for, renew, upgrade or cancel a deployed agent\'s plan, or buy extra credits. Charges '
|
|
558
|
+
+ 'come from the Wiro wallet of the agent\'s workspace (the team wallet for a team agent); the connected '
|
|
559
|
+
+ 'project must belong to that same workspace. Every call without confirm: true only returns what will happen '
|
|
560
|
+
+ 'and the amount (for a prorated upgrade, an estimate and its maximum). Show it to the user, get '
|
|
561
|
+
+ 'their approval, then repeat the call with confirm: true and confirm_charge_usd set to the quoted '
|
|
562
|
+
+ 'amount (for an upgrade, its maximum); if the price changed in between, nothing is charged and a '
|
|
563
|
+
+ 'new quote comes back. Never repeat a confirmed charge that failed or timed out without checking '
|
|
564
|
+
+ 'get_my_agent first. '
|
|
565
|
+
+ 'subscribe starts a plan for an agent without one; renew undoes a cancellation or renews an expired '
|
|
566
|
+
+ 'wallet plan; upgrade_to_pro charges the prorated difference now; buy_credits adds a credit pack '
|
|
567
|
+
+ '(Pro only); cancel stops renewals with no refund. Plans renew every 30 days until cancelled. An '
|
|
568
|
+
+ 'agent whose plan is billed by card is upgraded and topped up in the Wiro dashboard, and a personal '
|
|
569
|
+
+ 'card plan is cancelled there; the tool returns the link and charges nothing.',
|
|
570
|
+
inputSchema: {
|
|
571
|
+
agent_guid: z.string().min(1).max(100)
|
|
572
|
+
.describe('The deployed agent\'s guid, from list_my_agents or deploy_agent.'),
|
|
573
|
+
action: z.enum(ACTIONS)
|
|
574
|
+
.describe('subscribe, upgrade_to_pro, cancel, renew or buy_credits.'),
|
|
575
|
+
tier: z.enum(['starter', 'pro']).optional()
|
|
576
|
+
.describe('subscribe only: the tier to pay for (default: the agent\'s current tier).'),
|
|
577
|
+
pack: z.enum(['small', 'medium', 'large']).optional()
|
|
578
|
+
.describe('buy_credits only: the credit pack to buy. Leave it out to get the packs with their credits and prices.'),
|
|
579
|
+
request_id: z.string().regex(/^[A-Za-z0-9_-]{8,128}$/).optional()
|
|
580
|
+
.describe('buy_credits only: the id returned by the quote. Repeating a purchase with the same id never charges twice. Every new purchase needs a new quote; an id whose purchase already completed buys nothing.'),
|
|
581
|
+
confirm_charge_usd: z.number().min(0).max(100_000).optional()
|
|
582
|
+
.describe('With confirm: true, the amount the user approved, from the quote\'s nextAction (quote.chargeNowUsd, or quote.maxChargeUsd for upgrade_to_pro). Required for every action that charges: subscribe, upgrade_to_pro, buy_credits and renewing an expired plan. If a fresh quote no longer matches it, nothing is charged and the new quote is returned. For upgrade_to_pro it is the most the user agreed to pay now.'),
|
|
583
|
+
confirm: z.boolean().default(false)
|
|
584
|
+
.describe('false returns only the quote. true performs the action. Set it only after the user approved it.'),
|
|
585
|
+
},
|
|
586
|
+
outputSchema: {
|
|
587
|
+
action: z.enum(ACTIONS),
|
|
588
|
+
agentGuid: z.string(),
|
|
589
|
+
outcome: z.enum(['quote', 'done', 'not_needed', 'refused']),
|
|
590
|
+
quote: quoteSchema.optional(),
|
|
591
|
+
priceChanged: z.boolean().optional(),
|
|
592
|
+
result: resultSchema.optional(),
|
|
593
|
+
requestId: z.string().optional(),
|
|
594
|
+
dashboardUrl: z.string(),
|
|
595
|
+
agent: agentSummarySchema,
|
|
596
|
+
message: z.string(),
|
|
597
|
+
nextAction: nextActionSchema.optional(),
|
|
598
|
+
},
|
|
599
|
+
annotations: {
|
|
600
|
+
readOnlyHint: false,
|
|
601
|
+
destructiveHint: true,
|
|
602
|
+
idempotentHint: false,
|
|
603
|
+
openWorldHint: true,
|
|
604
|
+
},
|
|
605
|
+
}, async ({ agent_guid, action, tier, pack, request_id, confirm_charge_usd, confirm }, extra) => {
|
|
606
|
+
const guid = agent_guid.trim();
|
|
607
|
+
const requestId = action === 'buy_credits' ? request_id ?? newRequestId() : undefined;
|
|
608
|
+
let sentAtMs = 0;
|
|
609
|
+
let before;
|
|
610
|
+
let plan;
|
|
611
|
+
const respond = (payload) => {
|
|
612
|
+
const summary = toAgentSummary(payload.agentRow);
|
|
613
|
+
const dashboardUrl = before?.dashboardUrl ?? agentDashboardUrl(guid);
|
|
614
|
+
const heading = {
|
|
615
|
+
quote: 'Quote (nothing done yet)',
|
|
616
|
+
done: 'Done',
|
|
617
|
+
not_needed: 'Nothing to do',
|
|
618
|
+
refused: 'Not possible',
|
|
619
|
+
}[payload.outcome];
|
|
620
|
+
return {
|
|
621
|
+
content: [{
|
|
622
|
+
type: 'text',
|
|
623
|
+
text: [
|
|
624
|
+
`## ${action}: ${heading}`,
|
|
625
|
+
'',
|
|
626
|
+
payload.message,
|
|
627
|
+
'',
|
|
628
|
+
...formatAgentSummaryLines(summary),
|
|
629
|
+
`Dashboard: ${dashboardUrl}`,
|
|
630
|
+
].join('\n'),
|
|
631
|
+
}],
|
|
632
|
+
structuredContent: {
|
|
633
|
+
action,
|
|
634
|
+
agentGuid: guid,
|
|
635
|
+
outcome: payload.outcome,
|
|
636
|
+
...(payload.quote ? { quote: payload.quote } : {}),
|
|
637
|
+
...(payload.priceChanged ? { priceChanged: true } : {}),
|
|
638
|
+
...(payload.result ? { result: payload.result } : {}),
|
|
639
|
+
...(requestId && payload.withRequestId ? { requestId } : {}),
|
|
640
|
+
dashboardUrl,
|
|
641
|
+
agent: summary,
|
|
642
|
+
message: payload.message,
|
|
643
|
+
...(payload.nextAction ? { nextAction: payload.nextAction } : {}),
|
|
644
|
+
},
|
|
645
|
+
};
|
|
646
|
+
};
|
|
647
|
+
try {
|
|
648
|
+
const row = await readAgentDetail(client, guid, extra.signal);
|
|
649
|
+
const agent = toAgentDetail(row);
|
|
650
|
+
before = agent;
|
|
651
|
+
const raw = isPlainObject(row.subscription) ? row.subscription : null;
|
|
652
|
+
plan = planFor(client, action, agent, raw, { tier, pack, requestId: requestId ?? '' });
|
|
653
|
+
if (plan.outcome !== 'quote' || !plan.perform) {
|
|
654
|
+
return respond({ outcome: plan.outcome, message: plan.message, nextAction: plan.nextAction, agentRow: row });
|
|
655
|
+
}
|
|
656
|
+
const problem = confirm ? approvalProblem(action, plan.quote, confirm_charge_usd) : null;
|
|
657
|
+
if (!confirm || problem) {
|
|
658
|
+
const amount = approvalAmountOf(action, plan.quote);
|
|
659
|
+
const lead = problem === 'changed'
|
|
660
|
+
? `${changedLead(action, amount, confirm_charge_usd, request_id)}\n\n`
|
|
661
|
+
: problem === 'missing'
|
|
662
|
+
? 'Nothing was charged: confirm_charge_usd is missing. It is the amount the user approved, and it must be sent with confirm: true.\n\n'
|
|
663
|
+
: '';
|
|
664
|
+
const additions = [
|
|
665
|
+
...(requestId ? [`request_id "${requestId}"`] : []),
|
|
666
|
+
...(amount !== null ? [`confirm_charge_usd ${amount}`] : []),
|
|
667
|
+
'confirm: true',
|
|
668
|
+
];
|
|
669
|
+
return respond({
|
|
670
|
+
outcome: 'quote',
|
|
671
|
+
message: `${lead}${plan.message}\n\nNothing has been done yet. To go ahead, call manage_agent_billing again with the same arguments plus ${additions.join(', ')}.`,
|
|
672
|
+
quote: plan.quote,
|
|
673
|
+
...(problem === 'changed' ? { priceChanged: true } : {}),
|
|
674
|
+
nextAction: {
|
|
675
|
+
tool: 'manage_agent_billing',
|
|
676
|
+
arguments: confirmArguments(guid, action, plan.quote, requestId),
|
|
677
|
+
reason: plan.confirmReason ?? CHARGE_REASON,
|
|
678
|
+
},
|
|
679
|
+
agentRow: row,
|
|
680
|
+
withRequestId: true,
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
sentAtMs = Date.now();
|
|
684
|
+
const performed = await plan.perform(extra.signal);
|
|
685
|
+
let after = row;
|
|
686
|
+
let afterRead = false;
|
|
687
|
+
try {
|
|
688
|
+
after = await readAgentDetail(client, guid, extra.signal);
|
|
689
|
+
afterRead = true;
|
|
690
|
+
}
|
|
691
|
+
catch {
|
|
692
|
+
// The action went through; the earlier snapshot still identifies the agent.
|
|
693
|
+
}
|
|
694
|
+
// A request id that already completed a purchase is answered with
|
|
695
|
+
// success again, without a charge and without new credits. A purchase
|
|
696
|
+
// also retires any pack past its end date, so the extra credits can
|
|
697
|
+
// grow by less than the pack, and the agent's packs decide.
|
|
698
|
+
const credits = plan.quote?.creditsAdded ?? 0;
|
|
699
|
+
const extraBefore = toNumber(row.extracredits);
|
|
700
|
+
const extraAfter = afterRead ? toNumber(after.extracredits) : null;
|
|
701
|
+
if (action === 'buy_credits' && performed.outcome === 'done' && credits > 0
|
|
702
|
+
&& extraBefore !== null && extraAfter !== null && extraAfter - extraBefore < credits / 2) {
|
|
703
|
+
const fresh = await freshPackShows(client, guid, credits, sentAtMs, extra.signal);
|
|
704
|
+
if (fresh === false) {
|
|
705
|
+
return respond({
|
|
706
|
+
outcome: 'not_needed',
|
|
707
|
+
message: `Wiro answered request id "${requestId}" with a purchase that had already completed: the agent's extra credits did not grow and no new pack shows, so nothing new was bought or charged now. For another pack, get a new quote without request_id.`,
|
|
708
|
+
nextAction: billingCall(guid, 'buy_credits', { pack: plan.quote?.pack }),
|
|
709
|
+
agentRow: after,
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
return respond({
|
|
713
|
+
outcome: 'done',
|
|
714
|
+
message: fresh
|
|
715
|
+
? `${performed.message} The agent's extra credits grew by less than the pack, which happens when an older pack expires at the same moment. If this request id had already completed in an earlier call, it was charged only that once.`
|
|
716
|
+
: `Wiro accepted request id "${requestId}", but the agent's extra credits did not grow by the pack size and its packs could not be read, so it is not confirmed whether this bought a new pack or repeated a purchase that had already completed. Check get_my_agent and the wallet balance with get_account before buying again.`,
|
|
717
|
+
quote: plan.quote,
|
|
718
|
+
result: fresh ? performed.result : { chargedUsd: null },
|
|
719
|
+
nextAction: performed.nextAction,
|
|
720
|
+
agentRow: after,
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
return respond({
|
|
724
|
+
outcome: performed.outcome,
|
|
725
|
+
message: performed.message,
|
|
726
|
+
quote: plan.quote,
|
|
727
|
+
result: performed.result,
|
|
728
|
+
nextAction: performed.nextAction,
|
|
729
|
+
agentRow: after,
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
catch (error) {
|
|
733
|
+
if (error instanceof ApiResultError && requestId && /still pending\. Retry with the same request id/i.test(error.message)) {
|
|
734
|
+
return errorResult(new Error(`${error.message}\n\n${retryPurchaseText(guid, plan?.quote, requestId)}`));
|
|
735
|
+
}
|
|
736
|
+
if (sentAtMs > 0 && !extra.signal.aborted && outcomeUnknown(error)) {
|
|
737
|
+
const text = errorText(error);
|
|
738
|
+
const quote = plan?.quote;
|
|
739
|
+
if (chargeOf(quote) === null || !before) {
|
|
740
|
+
return errorResult(new Error(`${text}\n\nThe request was sent, so it may have taken effect. Check get_my_agent before trying again.`));
|
|
741
|
+
}
|
|
742
|
+
const observed = await observeOpenOutcome(client, action, before, quote, requestId, sentAtMs, extra.signal);
|
|
743
|
+
if (observed.happened && observed.row) {
|
|
744
|
+
return respond({
|
|
745
|
+
outcome: 'done',
|
|
746
|
+
message: `Wiro answered with an error (${text}) after the request was sent, but reading the agent again shows that it went through. ${observed.text}`,
|
|
747
|
+
quote,
|
|
748
|
+
result: observed.result,
|
|
749
|
+
nextAction: observed.nextAction,
|
|
750
|
+
agentRow: observed.row,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
const retry = action === 'buy_credits' && requestId
|
|
754
|
+
? retryPurchaseText(guid, quote, requestId)
|
|
755
|
+
: 'Do not repeat the call until this is checked.';
|
|
756
|
+
return errorResult(new Error(`${text}\n\nThe request was sent and the answer does not show whether it went through, so the wallet may have been charged. ${observed.text} ${retry}`));
|
|
757
|
+
}
|
|
758
|
+
if (error instanceof ApiResultError)
|
|
759
|
+
return errorResult(annotateKnownRefusal(error));
|
|
760
|
+
return errorResult(error);
|
|
761
|
+
}
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
//# sourceMappingURL=manage-agent-billing.js.map
|