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