Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
import type {
|
||||
BillingMutationResponse,
|
||||
BillingStateResponse,
|
||||
SubscriptionPreviewResponse,
|
||||
SubscriptionStateResponse,
|
||||
SubscriptionUpgradeResponse
|
||||
} from '../../../gatewayTypes.js'
|
||||
import { openExternalUrl } from '../../../lib/openExternalUrl.js'
|
||||
import type { SubscriptionOverlayCtx } from '../../interfaces.js'
|
||||
import { patchOverlayState } from '../../overlayStore.js'
|
||||
import type { SlashCommand, SlashRunCtx } from '../types.js'
|
||||
|
||||
type Sys = (text: string) => void
|
||||
|
||||
/**
|
||||
* Build the manage-subscription URL locally from the loaded subscription state.
|
||||
*
|
||||
* Uses `portal_url` (the resolved portal base URL carried in the state) and
|
||||
* `org_id` to construct `{portal_base}/manage-subscription?org_id=<id>`.
|
||||
* `org_id` pins the page to the correct account in multi-org situations.
|
||||
* Falls back to bare `/manage-subscription` if org_id is absent.
|
||||
*/
|
||||
function buildManageUrl(s: SubscriptionStateResponse, tierId?: string): string | null {
|
||||
// portal_url is already an absolute URL resolved by resolve_portal_base_url()
|
||||
// on the Python side (e.g. https://portal.nousresearch.com/billing). Strip any
|
||||
// path so we can attach /manage-subscription cleanly.
|
||||
let base: string | null = null
|
||||
|
||||
if (s.portal_url) {
|
||||
try {
|
||||
base = new URL(s.portal_url).origin
|
||||
} catch {
|
||||
// A malformed portal_url must not throw out of the Ink key handler
|
||||
// (it would crash the overlay) — treat it as "no manage URL".
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
if (!base) {
|
||||
return null
|
||||
}
|
||||
|
||||
const url = new URL('/manage-subscription', base)
|
||||
|
||||
if (s.org_id) {
|
||||
url.searchParams.set('org_id', s.org_id)
|
||||
}
|
||||
|
||||
if (tierId) {
|
||||
url.searchParams.set('plan', tierId)
|
||||
}
|
||||
|
||||
return url.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the ctx the overlay uses to talk to the gateway + emit transcript
|
||||
* lines. Mirrors topup.ts's buildOverlayCtx — all RPC + error-mapping logic
|
||||
* lives here (single source of truth); the overlay only renders + routes keys.
|
||||
*/
|
||||
const buildSubscriptionCtx = (
|
||||
ctx: SlashRunCtx,
|
||||
sys: Sys,
|
||||
initialState: SubscriptionStateResponse
|
||||
): SubscriptionOverlayCtx => ({
|
||||
fetchCard: () =>
|
||||
ctx.gateway
|
||||
.rpc<BillingStateResponse>('billing.state', {})
|
||||
.then(r => (r?.ok ? (r.card ?? null) : null))
|
||||
.catch(() => null),
|
||||
openManageLink: (tierId?: string) => {
|
||||
const url = buildManageUrl(initialState, tierId)
|
||||
|
||||
if (!url) {
|
||||
sys('Could not build manage URL — is your portal configured?')
|
||||
|
||||
return Promise.resolve(false)
|
||||
}
|
||||
|
||||
const opened = openExternalUrl(url)
|
||||
|
||||
if (opened) {
|
||||
sys('Opening your subscription page in the browser — finish there, then re-run /subscription.')
|
||||
} else {
|
||||
sys('Could not open browser — visit your subscription page manually at ' + url)
|
||||
}
|
||||
|
||||
return Promise.resolve(opened)
|
||||
},
|
||||
openPortal: (url: string) => {
|
||||
if (openExternalUrl(url)) {
|
||||
sys('Opening the portal in your browser — finish there, then re-run /subscription.')
|
||||
} else {
|
||||
sys('Could not open browser — visit ' + url + ' to finish.')
|
||||
}
|
||||
},
|
||||
preview: tierId =>
|
||||
ctx.gateway
|
||||
.rpc<SubscriptionPreviewResponse>('subscription.preview', { subscription_type_id: tierId })
|
||||
.then(r => r ?? null)
|
||||
.catch(() => null),
|
||||
refreshState: () =>
|
||||
ctx.gateway
|
||||
.rpc<SubscriptionStateResponse>('subscription.state', {})
|
||||
.then(r => r ?? null)
|
||||
.catch(() => null),
|
||||
requestRemoteSpending: () =>
|
||||
ctx.gateway
|
||||
.rpc<BillingMutationResponse>('billing.step_up', { session_id: ctx.sid ?? undefined })
|
||||
// Carry the typed denial (session_revoked / remote_spending_revoked /
|
||||
// rate_limited / …) so the stepup screen shows the right recovery.
|
||||
.then(r => ({ error: r?.error, granted: !!(r && r.ok && r.granted), message: r?.message }))
|
||||
.catch(() => ({
|
||||
granted: false,
|
||||
message: 'Could not reach the billing service — check your connection, then retry.'
|
||||
})),
|
||||
resume: () =>
|
||||
ctx.gateway
|
||||
.rpc<BillingMutationResponse>('subscription.resume', {})
|
||||
.then(r => r ?? null)
|
||||
.catch(() => null),
|
||||
scheduleCancellation: () =>
|
||||
ctx.gateway
|
||||
.rpc<BillingMutationResponse>('subscription.change', { cancel: true })
|
||||
.then(r => r ?? null)
|
||||
.catch(() => null),
|
||||
scheduleChange: tierId =>
|
||||
ctx.gateway
|
||||
.rpc<BillingMutationResponse>('subscription.change', { subscription_type_id: tierId })
|
||||
.then(r => r ?? null)
|
||||
.catch(() => null),
|
||||
sys,
|
||||
upgrade: (tierId, idempotencyKey) =>
|
||||
ctx.gateway
|
||||
.rpc<SubscriptionUpgradeResponse>('subscription.upgrade', {
|
||||
subscription_type_id: tierId,
|
||||
...(idempotencyKey ? { idempotency_key: idempotencyKey } : {})
|
||||
})
|
||||
.then(r => r ?? null)
|
||||
.catch(() => null)
|
||||
})
|
||||
|
||||
export const subscriptionCommands: SlashCommand[] = [
|
||||
{
|
||||
help: 'View or change your Nous subscription plan',
|
||||
name: 'subscription',
|
||||
aliases: ['upgrade'],
|
||||
// ZERO sub-commands: bare `/subscription` fetches state and opens the
|
||||
// overlay's in-terminal change flow (only /upgrade's charge_now confirm
|
||||
// moves money, via the V3 upgrade route).
|
||||
run: (_arg, ctx) => {
|
||||
const sys: Sys = ctx.transcript.sys
|
||||
|
||||
ctx.gateway
|
||||
.rpc<SubscriptionStateResponse>('subscription.state', {})
|
||||
.then(
|
||||
ctx.guarded<SubscriptionStateResponse>(s => {
|
||||
if (!s.logged_in) {
|
||||
sys('Not logged into Nous Portal — run /portal to log in, then /subscription.')
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
patchOverlayState({
|
||||
subscription: {
|
||||
ctx: buildSubscriptionCtx(ctx, sys, s),
|
||||
screen: 'overview',
|
||||
state: s
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
.catch(ctx.guardedErr)
|
||||
}
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user