@tangle-network/agent-app 0.49.38 → 0.50.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/dist/DesignCanvas-H6PVRRLU.js +12 -0
- package/dist/DesignCanvasEditor-ZHKJIDOB.js +15 -0
- package/dist/app-auth/index.js +3 -2
- package/dist/app-auth/index.js.map +1 -1
- package/dist/assistant/index.js +7 -7
- package/dist/chat-routes/index.js +10 -9
- package/dist/chat-routes/index.js.map +1 -1
- package/dist/chat-store/index.js +4 -4
- package/dist/{chunk-EA4UVS4T.js → chunk-3NCPXNBZ.js} +35 -90
- package/dist/chunk-3NCPXNBZ.js.map +1 -0
- package/dist/{chunk-C2VHOSY3.js → chunk-5P6TB74H.js} +11 -6
- package/dist/chunk-5P6TB74H.js.map +1 -0
- package/dist/{chunk-WAMJSZYB.js → chunk-ACFM2GFL.js} +3 -3
- package/dist/{chunk-XTLYE2VO.js → chunk-FRVVHMZO.js} +10 -10
- package/dist/{chunk-BWQPVS7D.js → chunk-H6XQZFTT.js} +2 -2
- package/dist/{chunk-FEVVF6JJ.js → chunk-HQ7R6CGM.js} +2 -2
- package/dist/{chunk-MIFVOYZ7.js → chunk-KJEW23GR.js} +3 -3
- package/dist/{chunk-G2B23BZ6.js → chunk-KU5YAGLB.js} +5 -5
- package/dist/{chunk-ZMMIQOFI.js → chunk-MX55JP7D.js} +5 -5
- package/dist/{chunk-S3344RBY.js → chunk-RACQDXJ7.js} +4 -4
- package/dist/{chunk-2WVBGQ2Y.js → chunk-RRWC77YY.js} +2 -2
- package/dist/chunk-TZEEGDHS.js +89 -0
- package/dist/chunk-TZEEGDHS.js.map +1 -0
- package/dist/{chunk-KWN5PGXM.js → chunk-YJYPXMDL.js} +3 -3
- package/dist/design-canvas/index.js +3 -2
- package/dist/design-canvas/index.js.map +1 -1
- package/dist/design-canvas-react/engine.js +5 -4
- package/dist/design-canvas-react/index.js +11 -10
- package/dist/design-canvas-react/index.js.map +1 -1
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/hosted-agent/application.d.ts +25 -0
- package/dist/hosted-agent/application.js +97 -0
- package/dist/hosted-agent/application.js.map +1 -0
- package/dist/hosted-agent/react/ApplicationLineSetup.d.ts +15 -0
- package/dist/hosted-agent/react/LineSetup.d.ts +1 -1
- package/dist/hosted-agent/react/contracts.d.ts +8 -1
- package/dist/hosted-agent/react/index.d.ts +1 -0
- package/dist/hosted-agent/react/index.js +90 -8
- package/dist/hosted-agent/react/index.js.map +1 -1
- package/dist/hosted-agent/react/lines.css +5 -0
- package/dist/hosted-agent/workspace-line.d.ts +9 -0
- package/dist/platform/index.js +3 -2
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/sso.d.ts +18 -4
- package/dist/public-consultation/index.js +7 -6
- package/dist/public-consultation/index.js.map +1 -1
- package/dist/sequences/index.js +2 -1
- package/dist/sequences/index.js.map +1 -1
- package/dist/stream/index.js +2 -2
- package/dist/web/core.d.ts +3 -1
- package/dist/web/index.js +5 -3
- package/dist/web-react/index.js +20 -20
- package/package.json +15 -10
- package/dist/DesignCanvas-YLVDCX6H.js +0 -11
- package/dist/DesignCanvasEditor-4QWY2LHM.js +0 -14
- package/dist/chunk-C2VHOSY3.js.map +0 -1
- package/dist/chunk-EA4UVS4T.js.map +0 -1
- /package/dist/{DesignCanvas-YLVDCX6H.js.map → DesignCanvas-H6PVRRLU.js.map} +0 -0
- /package/dist/{DesignCanvasEditor-4QWY2LHM.js.map → DesignCanvasEditor-ZHKJIDOB.js.map} +0 -0
- /package/dist/{chunk-WAMJSZYB.js.map → chunk-ACFM2GFL.js.map} +0 -0
- /package/dist/{chunk-XTLYE2VO.js.map → chunk-FRVVHMZO.js.map} +0 -0
- /package/dist/{chunk-BWQPVS7D.js.map → chunk-H6XQZFTT.js.map} +0 -0
- /package/dist/{chunk-FEVVF6JJ.js.map → chunk-HQ7R6CGM.js.map} +0 -0
- /package/dist/{chunk-MIFVOYZ7.js.map → chunk-KJEW23GR.js.map} +0 -0
- /package/dist/{chunk-G2B23BZ6.js.map → chunk-KU5YAGLB.js.map} +0 -0
- /package/dist/{chunk-ZMMIQOFI.js.map → chunk-MX55JP7D.js.map} +0 -0
- /package/dist/{chunk-S3344RBY.js.map → chunk-RACQDXJ7.js.map} +0 -0
- /package/dist/{chunk-2WVBGQ2Y.js.map → chunk-RRWC77YY.js.map} +0 -0
- /package/dist/{chunk-KWN5PGXM.js.map → chunk-YJYPXMDL.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/hosted-agent/react/LineSetup.tsx","../../../src/hosted-agent/react/LineMembers.tsx","../../../src/hosted-agent/react/LineBilling.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport type { Line } from '@tangle-network/sandbox'\nimport type {\n LineBoxMode, LineConnectionOption, LineIdentityKind, LineIdentityOption,\n LineSetupLine, LineSetupProps, LineSetupSnapshot,\n} from './contracts'\n\nconst useBrowserLayoutEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect\n\nconst TRANSPORT: Record<Line['transport'], string> = {\n imessage: 'iMessage', sms: 'SMS', whatsapp: 'WhatsApp', email: 'Email',\n}\nconst IDENTITY: Record<LineIdentityKind, string> = {\n handle: 'Inkbox handle', number: 'Dedicated number', email: 'Email line',\n}\n\ntype Choice = { key: string; connection: LineConnectionOption; identity: LineIdentityOption }\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : 'The line request failed. Try again.'\n}\n\nfunction dateTime(value: string): string {\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toLocaleString()\n}\n\nfunction choicesFor(snapshot: LineSetupSnapshot): Choice[] {\n return (snapshot.connections ?? []).flatMap(connection => connection.identities.map((identity, index) => ({\n key: `${connection.id}:${index}`,\n connection,\n identity,\n })))\n}\n\nfunction canOfferReconnect(line: LineSetupLine, choice: Choice): boolean {\n if (line.connectionId !== choice.connection.id || line.transport !== choice.identity.transport || line.targetId !== null) return false\n if (line.providerNumberId === null) return line.transport !== 'whatsapp' && !choice.identity.phoneNumberId && !choice.identity.requiresPhoneNumberId\n return choice.identity.phoneNumberId === line.providerNumberId ||\n (!choice.identity.phoneNumberId && choice.identity.requiresPhoneNumberId === true)\n}\n\nfunction matchesLineIdentity(line: LineSetupLine, identity: LineIdentityOption, numberId: string): boolean {\n if (line.transport !== identity.transport) return false\n if (line.providerNumberId === null) return line.transport !== 'whatsapp' && !identity.phoneNumberId && !identity.requiresPhoneNumberId\n return numberId === line.providerNumberId\n}\n\n/** Set up an owned identity through a host route backed by sandbox.lines and Hub. */\nexport function LineSetup({ client, scopeKey, initialTargetId, canManage, onNotice }: LineSetupProps) {\n const clientRef = useRef(client)\n clientRef.current = client\n const sequence = useRef(0)\n const incarnation = useRef(0)\n const confirmButton = useRef<HTMLButtonElement>(null)\n const heading = useRef<HTMLHeadingElement>(null)\n const disconnectTriggers = useRef(new Map<string, HTMLButtonElement>())\n const restoreFocusTo = useRef<string | null>(null)\n useBrowserLayoutEffect(() => {\n incarnation.current++\n return () => { incarnation.current++ }\n }, [scopeKey, initialTargetId])\n const [loadedSnapshot, setLoadedSnapshot] = useState<{ scopeKey: string; initialTargetId: string | undefined; value: LineSetupSnapshot } | null>(null)\n const snapshot = loadedSnapshot?.scopeKey === scopeKey && loadedSnapshot.initialTargetId === initialTargetId ? loadedSnapshot.value : null\n const [loading, setLoading] = useState(true)\n const [refreshFailed, setRefreshFailed] = useState(false)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [kind, setKind] = useState<LineIdentityKind>('handle')\n const [choiceKey, setChoiceKey] = useState('')\n const [targetId, setTargetId] = useState(initialTargetId ?? '')\n const [boxMode, setBoxMode] = useState<LineBoxMode>('per-member')\n const [phoneNumberId, setPhoneNumberId] = useState('')\n const [confirmLineId, setConfirmLineId] = useState<string | null>(null)\n\n function stillCurrent(requestIncarnation: number): boolean {\n return requestIncarnation === incarnation.current\n }\n\n async function reload(requestIncarnation: number): Promise<LineSetupSnapshot | null> {\n const request = ++sequence.current\n try {\n const next = await clientRef.current.load()\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return null\n setLoadedSnapshot({ scopeKey, initialTargetId, value: next })\n setError(null)\n setRefreshFailed(false)\n setTargetId(current => next.targets.some(target => target.id === current)\n ? current : next.targets.find(target => target.id === initialTargetId)?.id ?? next.targets[0]?.id ?? '')\n setLoading(false)\n return next\n } catch (cause) {\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return null\n setError(message(cause))\n setRefreshFailed(true)\n setLoading(false)\n return null\n }\n }\n\n function retry() {\n if (loading) return\n setLoading(true)\n void reload(incarnation.current)\n }\n\n function closeConfirmation(lineId: string) {\n restoreFocusTo.current = lineId\n setConfirmLineId(null)\n }\n\n useEffect(() => {\n setLoading(true)\n setLoadedSnapshot(null)\n setError(null)\n setRefreshFailed(false)\n setBusy(false)\n setKind('handle')\n setChoiceKey('')\n setTargetId(initialTargetId ?? '')\n setBoxMode('per-member')\n setPhoneNumberId('')\n setConfirmLineId(null)\n restoreFocusTo.current = null\n void reload(incarnation.current)\n return () => { sequence.current++ }\n }, [scopeKey, initialTargetId])\n\n useEffect(() => {\n if (confirmLineId) confirmButton.current?.focus()\n }, [confirmLineId])\n\n useEffect(() => {\n const lineId = restoreFocusTo.current\n if (confirmLineId !== null || lineId === null) return\n restoreFocusTo.current = null\n const trigger = disconnectTriggers.current.get(lineId)\n if (trigger && !trigger.disabled) trigger.focus()\n else heading.current?.focus()\n }, [confirmLineId, snapshot, refreshFailed])\n\n const allChoices = snapshot ? choicesFor(snapshot) : []\n const choices = snapshot ? allChoices.filter(choice => {\n const occupied = snapshot.lines.filter(line => line.status !== 'released' && line.transport === choice.identity.transport)\n const [line] = occupied\n return occupied.length === 0 || (occupied.length === 1 && line !== undefined && canOfferReconnect(line, choice))\n }) : []\n const availableKinds = (['handle', 'number', 'email'] as const).filter(value => choices.some(choice => choice.identity.kind === value))\n const selectedKind = availableKinds.includes(kind) ? kind : availableKinds[0]\n const kindChoices = choices.filter(choice => choice.identity.kind === selectedKind)\n const selected = kindChoices.find(choice => choice.key === choiceKey) ?? kindChoices[0]\n const selectedTarget = snapshot?.targets.find(target => target.id === targetId)\n const selectedMode = selectedTarget?.modes.includes(boxMode) ? boxMode : selectedTarget?.modes[0]\n const enteredNumber = selected?.identity.phoneNumberId ?? phoneNumberId.trim()\n const needsNumber = selected?.identity.transport === 'whatsapp' || selected?.identity.requiresPhoneNumberId\n const occupied = selected && snapshot?.lines.filter(line => line.status !== 'released' && line.transport === selected.identity.transport)\n const [occupiedLine] = occupied || []\n const reconnectChoice = Boolean(selected && occupied?.length === 1 && occupiedLine && canOfferReconnect(occupiedLine, selected))\n const reconnecting = Boolean(reconnectChoice && selected && occupiedLine &&\n matchesLineIdentity(occupiedLine, selected.identity, enteredNumber))\n const blockedByExistingLine = Boolean(occupied?.length && !reconnecting)\n\n async function connect() {\n if (!selected || !selectedTarget || !selectedMode || (needsNumber && !enteredNumber) || blockedByExistingLine || busy || loading || refreshFailed) return\n const requestIncarnation = incarnation.current\n setBusy(true)\n setError(null)\n try {\n await clientRef.current.connect({\n connectionId: selected.connection.id,\n transport: selected.identity.transport,\n ...(enteredNumber ? { phoneNumberId: enteredNumber } : {}),\n targetId: selectedTarget.id,\n boxMode: selectedMode,\n })\n if (!stillCurrent(requestIncarnation)) return\n const updated = await reload(requestIncarnation)\n if (!stillCurrent(requestIncarnation)) return\n if (!updated) {\n onNotice?.({ kind: 'error', message: 'Line connected, but its current status could not be loaded.' })\n return\n }\n const line = updated.lines.find(item => item.status !== 'released' && item.connectionId === selected.connection.id &&\n item.transport === selected.identity.transport && item.providerNumberId === (enteredNumber || null))\n onNotice?.({ kind: 'success', message: line?.answering ? 'The line is answering.' : 'Line connected. Add a member to start answers.' })\n } catch (cause) {\n if (!stillCurrent(requestIncarnation)) return\n const detail = message(cause)\n setError(detail)\n onNotice?.({ kind: 'error', message: detail })\n } finally {\n if (stillCurrent(requestIncarnation)) setBusy(false)\n }\n }\n\n async function disconnect(lineId: string) {\n if (busy || loading || refreshFailed) return\n const requestIncarnation = incarnation.current\n setBusy(true)\n setError(null)\n try {\n await clientRef.current.disconnect(lineId)\n if (!stillCurrent(requestIncarnation)) return\n const updated = await reload(requestIncarnation)\n if (!stillCurrent(requestIncarnation)) return\n closeConfirmation(lineId)\n onNotice?.(updated\n ? { kind: 'success', message: 'The line stopped answering.' }\n : { kind: 'error', message: 'Line disconnected, but its current status could not be loaded.' })\n } catch (cause) {\n if (!stillCurrent(requestIncarnation)) return\n const detail = message(cause)\n setError(detail)\n onNotice?.({ kind: 'error', message: detail })\n } finally {\n if (stillCurrent(requestIncarnation)) setBusy(false)\n }\n }\n\n return <section className=\"tangle-lines\" aria-label=\"Agent lines\">\n <header className=\"tangle-lines__heading\">\n <div>\n <h2 ref={heading} tabIndex={-1}>Lines</h2>\n {snapshot && <p>{snapshot.workspaceName} can use one line per transport.</p>}\n </div>\n {snapshot && <span className=\"tangle-lines__count\">{snapshot.lines.filter(line => line.answering).length} answering</span>}\n </header>\n\n {error && <div className=\"tangle-lines__error\" role=\"alert\">\n <span>{error}</span>\n {(!snapshot || refreshFailed) && <button type=\"button\" disabled={loading} onClick={retry}>Retry</button>}\n </div>}\n {loading && <p className=\"tangle-lines__muted\" role=\"status\">Loading lines…</p>}\n\n {snapshot && <>\n <div className=\"tangle-lines__list\">\n {snapshot.lines.filter(line => line.status !== 'released').length === 0 &&\n <p className=\"tangle-lines__empty\">No line answers for this workspace yet.</p>}\n {snapshot.lines.filter(line => line.status !== 'released').map(line =>\n <div className=\"tangle-lines__row\" key={line.id}>\n <div className=\"tangle-lines__row-main\">\n <span className=\"tangle-lines__eyebrow\">{TRANSPORT[line.transport]} · {line.answering ? 'Answering' : line.targetId === null ? 'Disconnected' : line.status === 'active' ? 'Awaiting member' : line.status}</span>\n <strong>{line.routerAddress ?? line.address}</strong>\n {line.connect && line.targetId !== null && <p>To reach this agent, text <code>{line.connect}</code>.</p>}\n <p>{line.targetLabel ?? 'No agent or box assigned'}{line.boxMode && ` · ${line.boxMode === 'per-member' ? 'one box per member' : 'shared box'}`}</p>\n {line.lastTurn.kind === 'latest' ? <p>Last turn: {line.lastTurn.status} · {dateTime(line.lastTurn.at)}</p>\n : <p>{line.lastTurn.kind === 'none' ? 'No turns yet.' : 'Turn history unavailable.'}</p>}\n </div>\n {canManage && line.canDisconnect && line.targetId !== null && line.status === 'active' && <div className=\"tangle-lines__row-actions\">\n {confirmLineId === line.id ? <div className=\"tangle-lines__confirm\" role=\"group\" aria-label={`Disconnect ${line.address}?`}>\n <span>Disconnect this line?</span>\n <button ref={confirmButton} type=\"button\" className=\"tangle-lines__danger\" disabled={busy || loading || refreshFailed} onClick={() => void disconnect(line.id)}>Disconnect</button>\n <button type=\"button\" disabled={busy} onClick={() => closeConfirmation(line.id)}>Keep line</button>\n </div> : <button ref={node => { if (node) disconnectTriggers.current.set(line.id, node); else disconnectTriggers.current.delete(line.id) }} type=\"button\" className=\"tangle-lines__quiet\" disabled={busy || loading || refreshFailed} onClick={() => setConfirmLineId(line.id)}>Disconnect line</button>}\n </div>}\n </div>)}\n </div>\n\n {canManage && (snapshot.connections === null || choices.length > 0 || allChoices.length === 0) && <div className=\"tangle-lines__setup\">\n <h3>Connect an identity you own</h3>\n <p>Choose an identity already connected in Hub. Billing details appear below.</p>\n {snapshot.connections === null ? <div className=\"tangle-lines__error\" role=\"status\">\n <span>Hub connections could not be read. Check Hub access and try again.</span>\n <button type=\"button\" disabled={loading} onClick={retry}>Retry</button>\n </div>\n : availableKinds.length === 0 ? <p className=\"tangle-lines__empty\">\n Connect an owned handle, number, or mailbox in Hub, then <button type=\"button\" disabled={loading} onClick={retry}>reload</button>.\n </p>\n : <>\n <div className=\"tangle-lines__kinds\" role=\"group\" aria-label=\"Identity type\">\n {availableKinds.map(value => <button key={value} type=\"button\" aria-pressed={selectedKind === value} onClick={() => { setKind(value); setChoiceKey(''); setPhoneNumberId('') }}>{IDENTITY[value]}</button>)}\n </div>\n <div className=\"tangle-lines__fields\">\n <label>Owned connection\n <select value={selected?.key ?? ''} onChange={event => { setChoiceKey(event.target.value); setPhoneNumberId('') }}>\n {kindChoices.map(choice => <option key={choice.key} value={choice.key}>{choice.connection.label} · {choice.identity.label}</option>)}\n </select>\n </label>\n <label>Agent or box\n <select value={selectedTarget?.id ?? ''} onChange={event => setTargetId(event.target.value)}>\n {snapshot.targets.map(target => <option key={target.id} value={target.id}>{target.label} · {target.kind}</option>)}\n </select>\n </label>\n {selectedTarget && selectedTarget.modes.length > 1 && <fieldset>\n <legend>Conversation space</legend>\n <label><input type=\"radio\" name={`line-box-mode-${scopeKey}`} value=\"per-member\" checked={selectedMode === 'per-member'} disabled={!selectedTarget?.modes.includes('per-member')} onChange={() => setBoxMode('per-member')} /> One box per member</label>\n <label><input type=\"radio\" name={`line-box-mode-${scopeKey}`} value=\"shared\" checked={selectedMode === 'shared'} disabled={!selectedTarget?.modes.includes('shared')} onChange={() => setBoxMode('shared')} /> Shared box for a team</label>\n </fieldset>}\n {needsNumber && !selected?.identity.phoneNumberId && <label>Existing provider number ID\n <input value={phoneNumberId} onChange={event => setPhoneNumberId(event.target.value)} autoComplete=\"off\" placeholder=\"Number ID from Hub\" />\n </label>}\n </div>\n {reconnectChoice && blockedByExistingLine && <p className=\"tangle-lines__warning\">Enter this line's current provider number ID to reconnect.</p>}\n <button className=\"tangle-lines__primary\" type=\"button\" disabled={busy || loading || refreshFailed || !selected || !selectedTarget || !selectedMode || (needsNumber && !enteredNumber) || blockedByExistingLine} onClick={() => void connect()}>\n {busy ? (reconnectChoice ? 'Reconnecting…' : 'Connecting…') : `${reconnectChoice ? 'Reconnect' : 'Connect'} ${selected ? TRANSPORT[selected.identity.transport] : 'line'}`}\n </button>\n </>}\n </div>}\n </>}\n </section>\n}\n","import { useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport type { LineMember } from '@tangle-network/sandbox'\nimport type { LineMembersProps } from './contracts'\n\nconst useBrowserLayoutEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect\n\nconst STATUS: Record<LineMember['status'], { label: string; detail: string }> = {\n invited: { label: 'Awaiting consent', detail: 'The first message from this address confirms consent.' },\n active: { label: 'Consented', detail: 'This member has sent a message to the line.' },\n stopped: { label: 'Stopped', detail: 'The member texted STOP. Only a message from them with START resumes replies.' },\n removed: { label: 'Removed', detail: 'This address no longer has access to the line.' },\n}\n\nfunction errorMessage(cause: unknown): string {\n return cause instanceof Error ? cause.message : 'The member request failed. Try again.'\n}\n\n/** Manage Hub line members while keeping consent and STOP authority with the sender. */\nexport function LineMembers({ lineId, scopeKey, client, roles, canManage, canAdd = true, maxMembers, allowRoleChange = true, allowRemoveLastOwner = false, onNotice }: LineMembersProps) {\n const clientRef = useRef(client)\n clientRef.current = client\n const sequence = useRef(0)\n const incarnation = useRef(0)\n const confirmButton = useRef<HTMLButtonElement>(null)\n const heading = useRef<HTMLHeadingElement>(null)\n const removeTriggers = useRef(new Map<string, HTMLButtonElement>())\n const restoreFocusTo = useRef<string | null>(null)\n useBrowserLayoutEffect(() => {\n incarnation.current++\n return () => { incarnation.current++ }\n }, [lineId, scopeKey])\n const [memberSnapshot, setMemberSnapshot] = useState<{ lineId: string; scopeKey: string; members: LineMember[] } | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [refreshFailed, setRefreshFailed] = useState(false)\n const [address, setAddress] = useState('')\n const [label, setLabel] = useState('')\n const [role, setRole] = useState('member')\n const [busy, setBusy] = useState<string | null>(null)\n const [confirmId, setConfirmId] = useState<string | null>(null)\n const members = memberSnapshot?.lineId === lineId && memberSnapshot.scopeKey === scopeKey ? memberSnapshot.members : null\n\n function stillCurrent(requestIncarnation: number): boolean {\n return requestIncarnation === incarnation.current\n }\n\n async function reload(requestIncarnation: number): Promise<boolean> {\n const request = ++sequence.current\n try {\n const next = await clientRef.current.list(lineId)\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return false\n setMemberSnapshot({ lineId, scopeKey, members: next })\n setError(null)\n setRefreshFailed(false)\n return true\n } catch (cause) {\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return false\n setError(errorMessage(cause))\n setRefreshFailed(true)\n return false\n }\n }\n\n async function retry() {\n if (busy) return\n const requestIncarnation = incarnation.current\n setBusy('refresh')\n try { await reload(requestIncarnation) }\n finally { if (stillCurrent(requestIncarnation)) setBusy(null) }\n }\n\n function closeConfirmation(memberId: string) {\n restoreFocusTo.current = memberId\n setConfirmId(null)\n }\n\n useEffect(() => {\n setMemberSnapshot(null)\n setError(null)\n setRefreshFailed(false)\n setAddress('')\n setLabel('')\n setRole('member')\n setConfirmId(null)\n restoreFocusTo.current = null\n setBusy(null)\n void reload(incarnation.current)\n return () => { sequence.current++ }\n }, [lineId, scopeKey])\n\n useEffect(() => {\n if (confirmId) confirmButton.current?.focus()\n }, [confirmId])\n\n useEffect(() => {\n const memberId = restoreFocusTo.current\n if (confirmId !== null || memberId === null) return\n restoreFocusTo.current = null\n const trigger = removeTriggers.current.get(memberId)\n if (trigger && !trigger.disabled) trigger.focus()\n else heading.current?.focus()\n }, [confirmId, members, refreshFailed])\n\n const selectedRole = roles.some(item => item.value === role) ? role : roles.find(item => item.value === 'member')?.value ?? roles[0]?.value ?? ''\n const activeMembers = members?.filter(member => member.status !== 'removed').length ?? 0\n const activeOwners = members?.filter(member => member.role === 'owner' && member.status !== 'removed').length ?? 0\n\n async function mutate(key: string, action: () => Promise<void>, success: string, onSuccess?: () => void) {\n if (busy || refreshFailed) return\n const requestIncarnation = incarnation.current\n setBusy(key)\n setError(null)\n try {\n await action()\n if (!stillCurrent(requestIncarnation)) return\n const refreshed = await reload(requestIncarnation)\n if (!stillCurrent(requestIncarnation)) return\n onSuccess?.()\n onNotice?.(refreshed\n ? { kind: 'success', message: success }\n : { kind: 'error', message: 'Member updated, but the current member list could not be loaded.' })\n } catch (cause) {\n if (!stillCurrent(requestIncarnation)) return\n const detail = errorMessage(cause)\n setError(detail)\n onNotice?.({ kind: 'error', message: detail })\n } finally {\n if (stillCurrent(requestIncarnation)) setBusy(null)\n }\n }\n\n function add() {\n const trimmed = address.trim()\n if (!trimmed || !selectedRole || members === null || (maxMembers !== undefined && activeMembers >= maxMembers)) return\n void mutate('add', () => clientRef.current.add(lineId, { address: trimmed, role: selectedRole, ...(label.trim() ? { label: label.trim() } : {}) }), 'Member invited. Their first message confirms consent.', () => {\n setAddress('')\n setLabel('')\n })\n }\n\n return <section className=\"tangle-lines\" aria-label=\"Line members\">\n <header className=\"tangle-lines__heading\">\n <div><h2 ref={heading} tabIndex={-1}>Members</h2><p>Each address controls its own consent and STOP state.</p></div>\n {members && <span className=\"tangle-lines__count\">{activeMembers} {activeMembers === 1 ? 'member' : 'members'}</span>}\n </header>\n {error && <div className=\"tangle-lines__error\" role=\"alert\">\n <span>{error}</span>\n {(members === null || refreshFailed) && <button type=\"button\" disabled={busy !== null} onClick={() => void retry()}>Retry</button>}\n </div>}\n {members === null ? !error && <p className=\"tangle-lines__muted\" role=\"status\">Loading members…</p> : <div className=\"tangle-lines__list\">\n {members.length === 0 && <p className=\"tangle-lines__empty\">{canAdd ? 'No members yet. Invite an address that you own or have permission to add.' : 'No members yet.'}</p>}\n {members.map(member => {\n const lastOwner = member.role === 'owner' && activeOwners <= 1\n const editable = canManage && member.status !== 'removed' && (!lastOwner || allowRemoveLastOwner)\n return <div className=\"tangle-lines__row\" key={member.id}>\n <div className=\"tangle-lines__row-main\">\n <span className={`tangle-lines__state tangle-lines__state--${member.status}`}>{STATUS[member.status].label}</span>\n <strong>{member.label || member.address}</strong>\n {member.label && <p>{member.address}</p>}\n <p>{STATUS[member.status].detail}</p>\n </div>\n <div className=\"tangle-lines__row-actions\">\n {editable ? <>\n {allowRoleChange && !lastOwner ? <label className=\"tangle-lines__compact-label\">Role\n <select value={member.role} disabled={busy !== null || refreshFailed} onChange={event => void mutate(member.id, () => clientRef.current.update(lineId, member.id, { role: event.target.value }), 'Role updated.')}>\n {!roles.some(item => item.value === member.role) && <option value={member.role}>{member.role}</option>}\n {roles.map(item => <option key={item.value} value={item.value}>{item.label}{item.tools === 'chat' ? ' · chat only' : ''}</option>)}\n </select>\n </label> : <span className=\"tangle-lines__role\">{roles.find(item => item.value === member.role)?.label ?? member.role}</span>}\n {confirmId === member.id ? <div className=\"tangle-lines__confirm\" role=\"group\" aria-label={`Remove ${member.label || member.address}?`}>\n <span>Remove {member.label || member.address}?</span>\n <button ref={confirmButton} type=\"button\" className=\"tangle-lines__danger\" disabled={busy !== null || refreshFailed} onClick={() => void mutate(member.id, () => clientRef.current.remove(lineId, member.id), 'Member removed.', () => closeConfirmation(member.id))}>Remove</button>\n <button type=\"button\" disabled={busy !== null} onClick={() => closeConfirmation(member.id)}>Keep</button>\n </div> : <button ref={node => { if (node) removeTriggers.current.set(member.id, node); else removeTriggers.current.delete(member.id) }} type=\"button\" className=\"tangle-lines__quiet\" disabled={busy !== null || refreshFailed} onClick={() => setConfirmId(member.id)}>Remove</button>}\n </> : <span className=\"tangle-lines__role\">{roles.find(item => item.value === member.role)?.label ?? member.role}</span>}\n </div>\n </div>\n })}\n </div>}\n\n {canManage && canAdd && members !== null && (maxMembers === undefined || activeMembers < maxMembers) && <div className=\"tangle-lines__setup\">\n <h3>Invite a member</h3>\n <p>Hub marks the address as invited. The member confirms by messaging the line.</p>\n <div className=\"tangle-lines__fields\">\n <label>Phone number or email\n <input value={address} onChange={event => setAddress(event.target.value)} autoComplete=\"off\" placeholder=\"+15551234567 or name@example.com\" />\n </label>\n <label>Name (optional)\n <input value={label} onChange={event => setLabel(event.target.value)} placeholder=\"Member name\" />\n </label>\n <label>Role\n <select value={selectedRole} disabled={roles.length === 0} onChange={event => setRole(event.target.value)}>\n {roles.map(item => <option key={item.value} value={item.value}>{item.label}{item.tools === 'chat' ? ' · chat only' : ''}</option>)}\n </select>\n </label>\n </div>\n <button type=\"button\" className=\"tangle-lines__primary\" disabled={busy !== null || refreshFailed || !address.trim() || !selectedRole} onClick={add}>\n {busy === 'add' ? 'Inviting…' : 'Invite member'}\n </button>\n </div>}\n </section>\n}\n","import type { LineBillingProps } from './contracts'\n\nfunction resetTime(value: string): string {\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toLocaleString()\n}\n\n/** Read-only billing facts from the line attachment and Hub allowance status. */\nexport function LineBilling({ view }: LineBillingProps) {\n return <section className=\"tangle-lines\" aria-label=\"Line billing\">\n <header className=\"tangle-lines__heading\">\n <div><h2>Billing</h2><p>{view.lineAddress}</p></div>\n </header>\n <dl className=\"tangle-lines__billing\">\n <div>\n <dt>Line</dt>\n <dd>{view.linePayer.kind === 'workspace' ? view.linePayer.label : 'Payer not verified'}</dd>\n {view.linePayer.kind === 'workspace' && <dd className=\"tangle-lines__billing-detail\">Charged to {view.workspaceName}.</dd>}\n </div>\n <div>\n <dt>Agent turns</dt>\n <dd>{view.turnPayer.kind === 'unverified' ? 'Payer not verified' : view.turnPayer.label}</dd>\n {view.turnPayer.kind === 'member' && <dd className=\"tangle-lines__billing-detail\">Each member pays for their own admitted turns.</dd>}\n </div>\n <div>\n <dt>Allowance</dt>\n {view.allowance ? <>\n <dd>{view.allowance.turnsPerMemberPerDay} turns per member each day</dd>\n {view.allowance.used !== undefined && view.allowance.limit !== undefined &&\n <dd className=\"tangle-lines__billing-detail\">{view.allowance.used} of {view.allowance.limit} turns used{view.allowance.resetsAt ? ` · resets ${resetTime(view.allowance.resetsAt)}` : ''}</dd>}\n </> : <dd>No allowance reported</dd>}\n </div>\n </dl>\n </section>\n}\n"],"mappings":"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","names":["occupied","line","useEffect","useLayoutEffect","useRef","useState","Fragment","jsx","jsxs","useBrowserLayoutEffect","Fragment","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/hosted-agent/react/LineSetup.tsx","../../../src/hosted-agent/react/LineMembers.tsx","../../../src/hosted-agent/react/LineBilling.tsx","../../../src/hosted-agent/react/ApplicationLineSetup.tsx"],"sourcesContent":["import { useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport type { Line } from '@tangle-network/sandbox'\nimport type {\n LineBoxMode, LineConnectionOption, LineIdentityKind, LineIdentityOption,\n LineSetupLine, LineSetupProps, LineSetupSnapshot,\n} from './contracts'\n\nconst useBrowserLayoutEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect\n\nconst TRANSPORT: Record<Line['transport'], string> = {\n imessage: 'iMessage', sms: 'SMS', whatsapp: 'WhatsApp', email: 'Email',\n}\nconst IDENTITY: Record<LineIdentityKind, string> = {\n handle: 'Inkbox handle', number: 'Dedicated number', email: 'Email line',\n}\n\ntype Choice = { key: string; connection: LineConnectionOption; identity: LineIdentityOption }\n\nfunction message(error: unknown): string {\n return error instanceof Error ? error.message : 'The line request failed. Try again.'\n}\n\nfunction dateTime(value: string): string {\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toLocaleString()\n}\n\nfunction choicesFor(snapshot: LineSetupSnapshot): Choice[] {\n return (snapshot.connections ?? []).flatMap(connection => connection.identities.map((identity, index) => ({\n key: `${connection.id}:${index}`,\n connection,\n identity,\n })))\n}\n\nfunction canOfferReconnect(line: LineSetupLine, choice: Choice): boolean {\n if (line.connectionId !== choice.connection.id || line.transport !== choice.identity.transport || line.targetId !== null) return false\n if (line.providerNumberId === null) return line.transport !== 'whatsapp' && !choice.identity.phoneNumberId && !choice.identity.requiresPhoneNumberId\n return choice.identity.phoneNumberId === line.providerNumberId ||\n (!choice.identity.phoneNumberId && choice.identity.requiresPhoneNumberId === true)\n}\n\nfunction matchesLineIdentity(line: LineSetupLine, identity: LineIdentityOption, numberId: string): boolean {\n if (line.transport !== identity.transport) return false\n if (line.providerNumberId === null) return line.transport !== 'whatsapp' && !identity.phoneNumberId && !identity.requiresPhoneNumberId\n return numberId === line.providerNumberId\n}\n\n/** Set up an owned identity through a host route backed by sandbox.lines and Hub. */\nexport function LineSetup({ client, scopeKey, initialTargetId, targetLabel, canManage, canConnect = canManage, showConnectionSetup = true, onNotice }: LineSetupProps) {\n const clientRef = useRef(client)\n clientRef.current = client\n const sequence = useRef(0)\n const incarnation = useRef(0)\n const confirmButton = useRef<HTMLButtonElement>(null)\n const heading = useRef<HTMLHeadingElement>(null)\n const disconnectTriggers = useRef(new Map<string, HTMLButtonElement>())\n const restoreFocusTo = useRef<string | null>(null)\n useBrowserLayoutEffect(() => {\n incarnation.current++\n return () => { incarnation.current++ }\n }, [scopeKey, initialTargetId])\n const [loadedSnapshot, setLoadedSnapshot] = useState<{ scopeKey: string; initialTargetId: string | undefined; value: LineSetupSnapshot } | null>(null)\n const snapshot = loadedSnapshot?.scopeKey === scopeKey && loadedSnapshot.initialTargetId === initialTargetId ? loadedSnapshot.value : null\n const [loading, setLoading] = useState(true)\n const [refreshFailed, setRefreshFailed] = useState(false)\n const [busy, setBusy] = useState(false)\n const [error, setError] = useState<string | null>(null)\n const [kind, setKind] = useState<LineIdentityKind>('handle')\n const [choiceKey, setChoiceKey] = useState('')\n const [targetId, setTargetId] = useState(initialTargetId ?? '')\n const [boxMode, setBoxMode] = useState<LineBoxMode>('per-member')\n const [phoneNumberId, setPhoneNumberId] = useState('')\n const [confirmLineId, setConfirmLineId] = useState<string | null>(null)\n\n function stillCurrent(requestIncarnation: number): boolean {\n return requestIncarnation === incarnation.current\n }\n\n async function reload(requestIncarnation: number): Promise<LineSetupSnapshot | null> {\n const request = ++sequence.current\n try {\n const next = await clientRef.current.load()\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return null\n setLoadedSnapshot({ scopeKey, initialTargetId, value: next })\n setError(null)\n setRefreshFailed(false)\n setTargetId(current => next.targets.some(target => target.id === current)\n ? current : next.targets.find(target => target.id === initialTargetId)?.id ?? next.targets[0]?.id ?? '')\n setLoading(false)\n return next\n } catch (cause) {\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return null\n setError(message(cause))\n setRefreshFailed(true)\n setLoading(false)\n return null\n }\n }\n\n function retry() {\n if (loading) return\n setLoading(true)\n void reload(incarnation.current)\n }\n\n function closeConfirmation(lineId: string) {\n restoreFocusTo.current = lineId\n setConfirmLineId(null)\n }\n\n useEffect(() => {\n setLoading(true)\n setLoadedSnapshot(null)\n setError(null)\n setRefreshFailed(false)\n setBusy(false)\n setKind('handle')\n setChoiceKey('')\n setTargetId(initialTargetId ?? '')\n setBoxMode('per-member')\n setPhoneNumberId('')\n setConfirmLineId(null)\n restoreFocusTo.current = null\n void reload(incarnation.current)\n return () => { sequence.current++ }\n }, [scopeKey, initialTargetId])\n\n useEffect(() => {\n if (confirmLineId) confirmButton.current?.focus()\n }, [confirmLineId])\n\n useEffect(() => {\n const lineId = restoreFocusTo.current\n if (confirmLineId !== null || lineId === null) return\n restoreFocusTo.current = null\n const trigger = disconnectTriggers.current.get(lineId)\n if (trigger && !trigger.disabled) trigger.focus()\n else heading.current?.focus()\n }, [confirmLineId, snapshot, refreshFailed])\n\n const allChoices = snapshot ? choicesFor(snapshot) : []\n const choices = snapshot ? allChoices.filter(choice => {\n const occupied = snapshot.lines.filter(line => line.status !== 'released' && line.transport === choice.identity.transport)\n const [line] = occupied\n return occupied.length === 0 || (occupied.length === 1 && line !== undefined && canOfferReconnect(line, choice))\n }) : []\n const availableKinds = (['handle', 'number', 'email'] as const).filter(value => choices.some(choice => choice.identity.kind === value))\n const selectedKind = availableKinds.includes(kind) ? kind : availableKinds[0]\n const kindChoices = choices.filter(choice => choice.identity.kind === selectedKind)\n const selected = kindChoices.find(choice => choice.key === choiceKey) ?? kindChoices[0]\n const selectedTarget = snapshot?.targets.find(target => target.id === targetId)\n const selectedMode = selectedTarget?.modes.includes(boxMode) ? boxMode : selectedTarget?.modes[0]\n const enteredNumber = selected?.identity.phoneNumberId ?? phoneNumberId.trim()\n const needsNumber = selected?.identity.transport === 'whatsapp' || selected?.identity.requiresPhoneNumberId\n const occupied = selected && snapshot?.lines.filter(line => line.status !== 'released' && line.transport === selected.identity.transport)\n const [occupiedLine] = occupied || []\n const reconnectChoice = Boolean(selected && occupied?.length === 1 && occupiedLine && canOfferReconnect(occupiedLine, selected))\n const reconnecting = Boolean(reconnectChoice && selected && occupiedLine &&\n matchesLineIdentity(occupiedLine, selected.identity, enteredNumber))\n const blockedByExistingLine = Boolean(occupied?.length && !reconnecting)\n\n async function connect() {\n if (!canManage || !canConnect || !selected || !selectedTarget || !selectedMode || (needsNumber && !enteredNumber) || blockedByExistingLine || busy || loading || refreshFailed) return\n const requestIncarnation = incarnation.current\n setBusy(true)\n setError(null)\n try {\n await clientRef.current.connect({\n connectionId: selected.connection.id,\n transport: selected.identity.transport,\n ...(enteredNumber ? { phoneNumberId: enteredNumber } : {}),\n targetId: selectedTarget.id,\n boxMode: selectedMode,\n })\n if (!stillCurrent(requestIncarnation)) return\n const updated = await reload(requestIncarnation)\n if (!stillCurrent(requestIncarnation)) return\n if (!updated) {\n onNotice?.({ kind: 'error', message: 'Line connected, but its current status could not be loaded.' })\n return\n }\n const line = updated.lines.find(item => item.status !== 'released' && item.connectionId === selected.connection.id &&\n item.transport === selected.identity.transport && item.providerNumberId === (enteredNumber || null))\n onNotice?.({ kind: 'success', message: line?.answering ? 'The line is answering.' : 'Line connected. Add a member to start answers.' })\n } catch (cause) {\n if (!stillCurrent(requestIncarnation)) return\n const detail = message(cause)\n setError(detail)\n onNotice?.({ kind: 'error', message: detail })\n } finally {\n if (stillCurrent(requestIncarnation)) setBusy(false)\n }\n }\n\n async function disconnect(lineId: string) {\n if (busy || loading || refreshFailed) return\n const requestIncarnation = incarnation.current\n setBusy(true)\n setError(null)\n try {\n const expectedAttachmentId = snapshot?.lines.find(line => line.id === lineId)?.attachmentId ?? undefined\n await clientRef.current.disconnect(lineId, expectedAttachmentId)\n if (!stillCurrent(requestIncarnation)) return\n const updated = await reload(requestIncarnation)\n if (!stillCurrent(requestIncarnation)) return\n closeConfirmation(lineId)\n onNotice?.(updated\n ? { kind: 'success', message: 'The line stopped answering.' }\n : { kind: 'error', message: 'Line disconnected, but its current status could not be loaded.' })\n } catch (cause) {\n if (!stillCurrent(requestIncarnation)) return\n const detail = message(cause)\n setError(detail)\n onNotice?.({ kind: 'error', message: detail })\n } finally {\n if (stillCurrent(requestIncarnation)) setBusy(false)\n }\n }\n\n return <section className=\"tangle-lines\" aria-label=\"Agent lines\">\n <header className=\"tangle-lines__heading\">\n <div>\n <h2 ref={heading} tabIndex={-1}>Lines</h2>\n {snapshot && <p>{snapshot.workspaceName} can use one line per transport.</p>}\n </div>\n {snapshot && <span className=\"tangle-lines__count\">{snapshot.lines.filter(line => line.answering).length} answering</span>}\n </header>\n\n {error && <div className=\"tangle-lines__error\" role=\"alert\">\n <span>{error}</span>\n {(!snapshot || refreshFailed) && <button type=\"button\" disabled={loading} onClick={retry}>Retry</button>}\n </div>}\n {loading && <p className=\"tangle-lines__muted\" role=\"status\">Loading lines…</p>}\n\n {snapshot && <>\n <div className=\"tangle-lines__list\">\n {snapshot.lines.filter(line => line.status !== 'released').length === 0 &&\n <p className=\"tangle-lines__empty\">No line answers for this workspace yet.</p>}\n {snapshot.lines.filter(line => line.status !== 'released').map(line =>\n <div className=\"tangle-lines__row\" key={line.id}>\n <div className=\"tangle-lines__row-main\">\n <span className=\"tangle-lines__eyebrow\">{TRANSPORT[line.transport]} · {line.answering ? 'Answering' : line.targetId === null ? 'Disconnected' : line.status === 'active' ? 'Awaiting member' : line.status}</span>\n <strong>{line.routerAddress ?? line.address}</strong>\n {line.connect && line.targetId !== null && <p>To reach this agent, text <code>{line.connect}</code>.</p>}\n <p>{line.targetLabel ?? 'No agent or box assigned'}{line.boxMode && ` · ${line.boxMode === 'per-member' ? 'one box per member' : 'shared box'}`}</p>\n {line.lastTurn.kind === 'latest' ? <p>Last turn: {line.lastTurn.status} · {dateTime(line.lastTurn.at)}</p>\n : <p>{line.lastTurn.kind === 'none' ? 'No turns yet.' : 'Turn history unavailable.'}</p>}\n </div>\n {canManage && line.canDisconnect && line.targetId !== null && line.status === 'active' && <div className=\"tangle-lines__row-actions\">\n {confirmLineId === line.id ? <div className=\"tangle-lines__confirm\" role=\"group\" aria-label={`Disconnect ${line.address}?`}>\n <span>Disconnect this line?</span>\n <button ref={confirmButton} type=\"button\" className=\"tangle-lines__danger\" disabled={busy || loading || refreshFailed} onClick={() => void disconnect(line.id)}>Disconnect</button>\n <button type=\"button\" disabled={busy} onClick={() => closeConfirmation(line.id)}>Keep line</button>\n </div> : <button ref={node => { if (node) disconnectTriggers.current.set(line.id, node); else disconnectTriggers.current.delete(line.id) }} type=\"button\" className=\"tangle-lines__quiet\" disabled={busy || loading || refreshFailed} onClick={() => setConfirmLineId(line.id)}>Disconnect line</button>}\n </div>}\n </div>)}\n </div>\n\n {canManage && showConnectionSetup && (snapshot.connections === null || choices.length > 0 || allChoices.length === 0) && <div className=\"tangle-lines__setup\">\n <h3>Connect an identity you own</h3>\n <p>Choose an identity already connected in Hub. Billing details appear below.</p>\n {snapshot.connections === null ? <div className=\"tangle-lines__error\" role=\"status\">\n <span>Hub connections could not be read. Check Hub access and try again.</span>\n <button type=\"button\" disabled={loading} onClick={retry}>Retry</button>\n </div>\n : availableKinds.length === 0 ? <p className=\"tangle-lines__empty\">\n Connect an owned handle, number, or mailbox in Hub, then <button type=\"button\" disabled={loading} onClick={retry}>reload</button>.\n </p>\n : <>\n <div className=\"tangle-lines__kinds\" role=\"group\" aria-label=\"Identity type\">\n {availableKinds.map(value => <button key={value} type=\"button\" aria-pressed={selectedKind === value} onClick={() => { setKind(value); setChoiceKey(''); setPhoneNumberId('') }}>{IDENTITY[value]}</button>)}\n </div>\n <div className=\"tangle-lines__fields\">\n <label>Owned connection\n <select value={selected?.key ?? ''} onChange={event => { setChoiceKey(event.target.value); setPhoneNumberId('') }}>\n {kindChoices.map(choice => <option key={choice.key} value={choice.key}>{choice.connection.label} · {choice.identity.label}</option>)}\n </select>\n </label>\n <label>{targetLabel ?? 'Agent or box'}\n <select value={selectedTarget?.id ?? ''} onChange={event => setTargetId(event.target.value)}>\n {snapshot.targets.map(target => <option key={target.id} value={target.id}>{target.label}{targetLabel ? '' : ` · ${target.kind}`}</option>)}\n </select>\n </label>\n {selectedTarget && selectedTarget.modes.length > 1 && <fieldset>\n <legend>Conversation space</legend>\n <label><input type=\"radio\" name={`line-box-mode-${scopeKey}`} value=\"per-member\" checked={selectedMode === 'per-member'} disabled={!selectedTarget?.modes.includes('per-member')} onChange={() => setBoxMode('per-member')} /> One box per member</label>\n <label><input type=\"radio\" name={`line-box-mode-${scopeKey}`} value=\"shared\" checked={selectedMode === 'shared'} disabled={!selectedTarget?.modes.includes('shared')} onChange={() => setBoxMode('shared')} /> Shared box for a team</label>\n </fieldset>}\n {needsNumber && !selected?.identity.phoneNumberId && <label>Existing provider number ID\n <input value={phoneNumberId} onChange={event => setPhoneNumberId(event.target.value)} autoComplete=\"off\" placeholder=\"Number ID from Hub\" />\n </label>}\n </div>\n {reconnectChoice && blockedByExistingLine && <p className=\"tangle-lines__warning\">Enter this line's current provider number ID to reconnect.</p>}\n <button className=\"tangle-lines__primary\" type=\"button\" disabled={!canConnect || busy || loading || refreshFailed || !selected || !selectedTarget || !selectedMode || (needsNumber && !enteredNumber) || blockedByExistingLine} onClick={() => void connect()}>\n {busy ? (reconnectChoice ? 'Reconnecting…' : 'Connecting…') : `${reconnectChoice ? 'Reconnect' : 'Connect'} ${selected ? TRANSPORT[selected.identity.transport] : 'line'}`}\n </button>\n </>}\n </div>}\n </>}\n </section>\n}\n","import { useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport type { LineMember } from '@tangle-network/sandbox'\nimport type { LineMembersProps } from './contracts'\n\nconst useBrowserLayoutEffect = typeof document !== 'undefined' ? useLayoutEffect : useEffect\n\nconst STATUS: Record<LineMember['status'], { label: string; detail: string }> = {\n invited: { label: 'Awaiting consent', detail: 'The first message from this address confirms consent.' },\n active: { label: 'Consented', detail: 'This member has sent a message to the line.' },\n stopped: { label: 'Stopped', detail: 'The member texted STOP. Only a message from them with START resumes replies.' },\n removed: { label: 'Removed', detail: 'This address no longer has access to the line.' },\n}\n\nfunction errorMessage(cause: unknown): string {\n return cause instanceof Error ? cause.message : 'The member request failed. Try again.'\n}\n\n/** Manage Hub line members while keeping consent and STOP authority with the sender. */\nexport function LineMembers({ lineId, scopeKey, client, roles, canManage, canAdd = true, maxMembers, allowRoleChange = true, allowRemoveLastOwner = false, onNotice }: LineMembersProps) {\n const clientRef = useRef(client)\n clientRef.current = client\n const sequence = useRef(0)\n const incarnation = useRef(0)\n const confirmButton = useRef<HTMLButtonElement>(null)\n const heading = useRef<HTMLHeadingElement>(null)\n const removeTriggers = useRef(new Map<string, HTMLButtonElement>())\n const restoreFocusTo = useRef<string | null>(null)\n useBrowserLayoutEffect(() => {\n incarnation.current++\n return () => { incarnation.current++ }\n }, [lineId, scopeKey])\n const [memberSnapshot, setMemberSnapshot] = useState<{ lineId: string; scopeKey: string; members: LineMember[] } | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [refreshFailed, setRefreshFailed] = useState(false)\n const [address, setAddress] = useState('')\n const [label, setLabel] = useState('')\n const [role, setRole] = useState('member')\n const [busy, setBusy] = useState<string | null>(null)\n const [confirmId, setConfirmId] = useState<string | null>(null)\n const members = memberSnapshot?.lineId === lineId && memberSnapshot.scopeKey === scopeKey ? memberSnapshot.members : null\n\n function stillCurrent(requestIncarnation: number): boolean {\n return requestIncarnation === incarnation.current\n }\n\n async function reload(requestIncarnation: number): Promise<boolean> {\n const request = ++sequence.current\n try {\n const next = await clientRef.current.list(lineId)\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return false\n setMemberSnapshot({ lineId, scopeKey, members: next })\n setError(null)\n setRefreshFailed(false)\n return true\n } catch (cause) {\n if (request !== sequence.current || !stillCurrent(requestIncarnation)) return false\n setError(errorMessage(cause))\n setRefreshFailed(true)\n return false\n }\n }\n\n async function retry() {\n if (busy) return\n const requestIncarnation = incarnation.current\n setBusy('refresh')\n try { await reload(requestIncarnation) }\n finally { if (stillCurrent(requestIncarnation)) setBusy(null) }\n }\n\n function closeConfirmation(memberId: string) {\n restoreFocusTo.current = memberId\n setConfirmId(null)\n }\n\n useEffect(() => {\n setMemberSnapshot(null)\n setError(null)\n setRefreshFailed(false)\n setAddress('')\n setLabel('')\n setRole('member')\n setConfirmId(null)\n restoreFocusTo.current = null\n setBusy(null)\n void reload(incarnation.current)\n return () => { sequence.current++ }\n }, [lineId, scopeKey])\n\n useEffect(() => {\n if (confirmId) confirmButton.current?.focus()\n }, [confirmId])\n\n useEffect(() => {\n const memberId = restoreFocusTo.current\n if (confirmId !== null || memberId === null) return\n restoreFocusTo.current = null\n const trigger = removeTriggers.current.get(memberId)\n if (trigger && !trigger.disabled) trigger.focus()\n else heading.current?.focus()\n }, [confirmId, members, refreshFailed])\n\n const selectedRole = roles.some(item => item.value === role) ? role : roles.find(item => item.value === 'member')?.value ?? roles[0]?.value ?? ''\n const activeMembers = members?.filter(member => member.status !== 'removed').length ?? 0\n const activeOwners = members?.filter(member => member.role === 'owner' && member.status !== 'removed').length ?? 0\n\n async function mutate(key: string, action: () => Promise<void>, success: string, onSuccess?: () => void) {\n if (busy || refreshFailed) return\n const requestIncarnation = incarnation.current\n setBusy(key)\n setError(null)\n try {\n await action()\n if (!stillCurrent(requestIncarnation)) return\n const refreshed = await reload(requestIncarnation)\n if (!stillCurrent(requestIncarnation)) return\n onSuccess?.()\n onNotice?.(refreshed\n ? { kind: 'success', message: success }\n : { kind: 'error', message: 'Member updated, but the current member list could not be loaded.' })\n } catch (cause) {\n if (!stillCurrent(requestIncarnation)) return\n const detail = errorMessage(cause)\n setError(detail)\n onNotice?.({ kind: 'error', message: detail })\n } finally {\n if (stillCurrent(requestIncarnation)) setBusy(null)\n }\n }\n\n function add() {\n const trimmed = address.trim()\n if (!trimmed || !selectedRole || members === null || (maxMembers !== undefined && activeMembers >= maxMembers)) return\n void mutate('add', () => clientRef.current.add(lineId, { address: trimmed, role: selectedRole, ...(label.trim() ? { label: label.trim() } : {}) }), 'Member invited. Their first message confirms consent.', () => {\n setAddress('')\n setLabel('')\n })\n }\n\n return <section className=\"tangle-lines\" aria-label=\"Line members\">\n <header className=\"tangle-lines__heading\">\n <div><h2 ref={heading} tabIndex={-1}>Members</h2><p>Each address controls its own consent and STOP state.</p></div>\n {members && <span className=\"tangle-lines__count\">{activeMembers} {activeMembers === 1 ? 'member' : 'members'}</span>}\n </header>\n {error && <div className=\"tangle-lines__error\" role=\"alert\">\n <span>{error}</span>\n {(members === null || refreshFailed) && <button type=\"button\" disabled={busy !== null} onClick={() => void retry()}>Retry</button>}\n </div>}\n {members === null ? !error && <p className=\"tangle-lines__muted\" role=\"status\">Loading members…</p> : <div className=\"tangle-lines__list\">\n {members.length === 0 && <p className=\"tangle-lines__empty\">{canAdd ? 'No members yet. Invite an address that you own or have permission to add.' : 'No members yet.'}</p>}\n {members.map(member => {\n const lastOwner = member.role === 'owner' && activeOwners <= 1\n const editable = canManage && member.status !== 'removed' && (!lastOwner || allowRemoveLastOwner)\n return <div className=\"tangle-lines__row\" key={member.id}>\n <div className=\"tangle-lines__row-main\">\n <span className={`tangle-lines__state tangle-lines__state--${member.status}`}>{STATUS[member.status].label}</span>\n <strong>{member.label || member.address}</strong>\n {member.label && <p>{member.address}</p>}\n <p>{STATUS[member.status].detail}</p>\n </div>\n <div className=\"tangle-lines__row-actions\">\n {editable ? <>\n {allowRoleChange && !lastOwner ? <label className=\"tangle-lines__compact-label\">Role\n <select value={member.role} disabled={busy !== null || refreshFailed} onChange={event => void mutate(member.id, () => clientRef.current.update(lineId, member.id, { role: event.target.value }), 'Role updated.')}>\n {!roles.some(item => item.value === member.role) && <option value={member.role}>{member.role}</option>}\n {roles.map(item => <option key={item.value} value={item.value}>{item.label}{item.tools === 'chat' ? ' · chat only' : ''}</option>)}\n </select>\n </label> : <span className=\"tangle-lines__role\">{roles.find(item => item.value === member.role)?.label ?? member.role}</span>}\n {confirmId === member.id ? <div className=\"tangle-lines__confirm\" role=\"group\" aria-label={`Remove ${member.label || member.address}?`}>\n <span>Remove {member.label || member.address}?</span>\n <button ref={confirmButton} type=\"button\" className=\"tangle-lines__danger\" disabled={busy !== null || refreshFailed} onClick={() => void mutate(member.id, () => clientRef.current.remove(lineId, member.id), 'Member removed.', () => closeConfirmation(member.id))}>Remove</button>\n <button type=\"button\" disabled={busy !== null} onClick={() => closeConfirmation(member.id)}>Keep</button>\n </div> : <button ref={node => { if (node) removeTriggers.current.set(member.id, node); else removeTriggers.current.delete(member.id) }} type=\"button\" className=\"tangle-lines__quiet\" disabled={busy !== null || refreshFailed} onClick={() => setConfirmId(member.id)}>Remove</button>}\n </> : <span className=\"tangle-lines__role\">{roles.find(item => item.value === member.role)?.label ?? member.role}</span>}\n </div>\n </div>\n })}\n </div>}\n\n {canManage && canAdd && members !== null && (maxMembers === undefined || activeMembers < maxMembers) && <div className=\"tangle-lines__setup\">\n <h3>Invite a member</h3>\n <p>Hub marks the address as invited. The member confirms by messaging the line.</p>\n <div className=\"tangle-lines__fields\">\n <label>Phone number or email\n <input value={address} onChange={event => setAddress(event.target.value)} autoComplete=\"off\" placeholder=\"+15551234567 or name@example.com\" />\n </label>\n <label>Name (optional)\n <input value={label} onChange={event => setLabel(event.target.value)} placeholder=\"Member name\" />\n </label>\n <label>Role\n <select value={selectedRole} disabled={roles.length === 0} onChange={event => setRole(event.target.value)}>\n {roles.map(item => <option key={item.value} value={item.value}>{item.label}{item.tools === 'chat' ? ' · chat only' : ''}</option>)}\n </select>\n </label>\n </div>\n <button type=\"button\" className=\"tangle-lines__primary\" disabled={busy !== null || refreshFailed || !address.trim() || !selectedRole} onClick={add}>\n {busy === 'add' ? 'Inviting…' : 'Invite member'}\n </button>\n </div>}\n </section>\n}\n","import type { LineBillingProps } from './contracts'\n\nfunction resetTime(value: string): string {\n const date = new Date(value)\n return Number.isNaN(date.getTime()) ? value : date.toLocaleString()\n}\n\n/** Read-only billing facts from the line attachment and Hub allowance status. */\nexport function LineBilling({ view }: LineBillingProps) {\n return <section className=\"tangle-lines\" aria-label=\"Line billing\">\n <header className=\"tangle-lines__heading\">\n <div><h2>Billing</h2><p>{view.lineAddress}</p></div>\n </header>\n <dl className=\"tangle-lines__billing\">\n <div>\n <dt>Line</dt>\n <dd>{view.linePayer.kind === 'workspace' ? view.linePayer.label : 'Payer not verified'}</dd>\n {view.linePayer.kind === 'workspace' && <dd className=\"tangle-lines__billing-detail\">Charged to {view.workspaceName}.</dd>}\n </div>\n <div>\n <dt>Agent turns</dt>\n <dd>{view.turnPayer.kind === 'unverified' ? 'Payer not verified' : view.turnPayer.label}</dd>\n {view.turnPayer.kind === 'member' && <dd className=\"tangle-lines__billing-detail\">Each member pays for their own admitted turns.</dd>}\n </div>\n <div>\n <dt>Allowance</dt>\n {view.allowance ? <>\n <dd>{view.allowance.turnsPerMemberPerDay} turns per member each day</dd>\n {view.allowance.used !== undefined && view.allowance.limit !== undefined &&\n <dd className=\"tangle-lines__billing-detail\">{view.allowance.used} of {view.allowance.limit} turns used{view.allowance.resetsAt ? ` · resets ${resetTime(view.allowance.resetsAt)}` : ''}</dd>}\n </> : <dd>No allowance reported</dd>}\n </div>\n </dl>\n </section>\n}\n","import { useMemo, useState } from 'react'\nimport { LineSetup } from './LineSetup'\nimport type { LineConnectInput, LineSetupClient, LineSetupProps } from './contracts'\n\nexport interface ApplicationLineConnectInput extends LineConnectInput {\n operatorAddress: string\n turnsPerDay: number\n}\nexport interface ApplicationLineSetupClient extends Omit<LineSetupClient, 'connect'> {\n connect(input: ApplicationLineConnectInput): Promise<void>\n}\nexport interface ApplicationLineSetupProps extends Omit<LineSetupProps, 'client' | 'canConnect'> {\n client: ApplicationLineSetupClient\n /** Disable new grants without preventing disconnection of an existing line. */\n enabled: boolean\n}\n\n/** Nominate a sender explicitly, then use the existing connection and disconnect UI. */\nexport function ApplicationLineSetup(props: ApplicationLineSetupProps) {\n return <ApplicationLineSetupScope key={props.scopeKey} {...props} />\n}\n\nfunction ApplicationLineSetupScope({ client, enabled, ...props }: ApplicationLineSetupProps) {\n const [operatorAddress, setAddress] = useState('')\n const [limit, setLimit] = useState('20')\n const [approved, setApproved] = useState<string | null>(null)\n const address = operatorAddress.trim()\n const turnsPerDay = Number(limit)\n const nomination = JSON.stringify({ address, turnsPerDay })\n const valid = address.length <= 320 && (/^\\+[1-9]\\d{6,14}$/.test(address) || /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(address))\n && Number.isSafeInteger(turnsPerDay) && turnsPerDay >= 1 && turnsPerDay <= 10_000\n const canConnect = props.canManage && enabled && valid && approved === nomination\n const lineClient = useMemo<LineSetupClient>(() => ({\n load: () => client.load(),\n disconnect: (id, expectedAttachmentId) => client.disconnect(id, expectedAttachmentId),\n connect: input => {\n if (!canConnect) throw new Error('Confirm the sender and daily limit before connecting')\n return client.connect({ ...input, operatorAddress: address, turnsPerDay })\n },\n }), [client, canConnect, address, turnsPerDay])\n return <>\n {props.canManage && <section className=\"tangle-lines tangle-lines--application\" aria-label=\"Application access\">\n <h2>Text your workspace</h2>\n <p>Use the same agent, conversation and saved work. Only the sender you authorize can issue commands.</p>\n {!enabled ? <p role=\"status\">New connections are disabled. You can still disconnect an existing line below.</p> : <>\n <div className=\"tangle-lines__fields\">\n <label>Authorized sender\n <input value={operatorAddress} onChange={e => { setAddress(e.target.value); setApproved(null) }}\n autoComplete=\"off\" placeholder=\"+15550100001 or your Apple ID / email\" />\n </label>\n <label>Maximum messages per day\n <input type=\"number\" min={1} max={10_000} step={1} value={limit}\n onChange={e => { setLimit(e.target.value); setApproved(null) }} />\n </label>\n </div>\n <label className=\"tangle-lines__delegation\">\n <input type=\"checkbox\" checked={approved === nomination} disabled={!valid}\n onChange={e => setApproved(e.target.checked ? nomination : null)} />\n I authorize this sender to work in the conversation I select below using my application access and compute budget.\n </label>\n <p>The daily message limit is not a dollar cap. The first inbound message establishes messaging consent.\n STOP stops replies, not an already accepted task; cancel that task in the application.</p>\n </>}\n </section>}\n <LineSetup {...props} targetLabel={props.targetLabel ?? 'Conversation'} client={lineClient} canConnect={canConnect} showConnectionSetup={enabled} />\n </>\n}\n"],"mappings":"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","names":["occupied","line","useEffect","useLayoutEffect","useRef","useState","Fragment","jsx","jsxs","useBrowserLayoutEffect","Fragment","jsx","jsxs","useState","Fragment","jsx","jsxs","useState"]}
|
|
@@ -108,3 +108,8 @@
|
|
|
108
108
|
.tangle-lines__billing > div { grid-template-columns: 1fr; }
|
|
109
109
|
.tangle-lines__billing dd.tangle-lines__billing-detail { grid-column: 1; }
|
|
110
110
|
}
|
|
111
|
+
|
|
112
|
+
.tangle-lines__delegation { display: flex; align-items: flex-start; gap: .65rem; font-size: .9rem; line-height: 1.5; }
|
|
113
|
+
.tangle-lines__delegation input { flex: 0 0 auto; margin-top: .25rem; }
|
|
114
|
+
.tangle-lines--application { display: grid; gap: 1rem; margin-bottom: 1.5rem; }
|
|
115
|
+
.tangle-lines--application p { overflow-wrap: anywhere; }
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { LineApplication, LineAttachment, SandboxInstance } from '@tangle-network/sandbox/core';
|
|
2
|
+
/** A line delegates its nominated owner to the application's normal chat path. */
|
|
3
|
+
export declare function attachWorkspaceLine(input: {
|
|
4
|
+
box: Pick<SandboxInstance, 'lines'>;
|
|
5
|
+
lineId: string;
|
|
6
|
+
ownerAddress: string;
|
|
7
|
+
application: LineApplication;
|
|
8
|
+
turnsPerDay?: number;
|
|
9
|
+
}): Promise<LineAttachment>;
|
package/dist/platform/index.js
CHANGED
|
@@ -13,12 +13,13 @@ import {
|
|
|
13
13
|
resolveTangleSsoAccount,
|
|
14
14
|
signSessionCookieValue,
|
|
15
15
|
verifySignedSsoState
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-5P6TB74H.js";
|
|
17
17
|
import {
|
|
18
18
|
resolveTangleDevOrUserKey,
|
|
19
19
|
resolveTangleExecutionEnvironment
|
|
20
20
|
} from "../chunk-JML7WKWU.js";
|
|
21
|
-
import "../chunk-
|
|
21
|
+
import "../chunk-TZEEGDHS.js";
|
|
22
|
+
import "../chunk-3NCPXNBZ.js";
|
|
22
23
|
import "../chunk-TXD5HXLE.js";
|
|
23
24
|
|
|
24
25
|
// src/platform/hub.ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/platform/hub.ts","../../src/platform/billing.ts","../../src/platform/api-key-auth.ts"],"sourcesContent":["/**\n * Integrations-hub proxy routes: the app-side surface that forwards an\n * authenticated user's requests to the platform's `/v1/integrations/*` API\n * using their stored platform key. Auth, key lookup, and the wire client are\n * structural seams (`HubProxyContext`); error detection is by name + shape so\n * it survives bundlers duplicating module instances.\n */\n\nimport {\n resolveTangleDevOrUserKey,\n type TangleExecutionEnvironment,\n type TangleExecutionKeySource,\n} from '../runtime/model'\n\n/** Hub bearer provenance mirrors the execution-key source union. */\nexport type TangleHubBearerSource = TangleExecutionKeySource\n\n/** Represent a resolved bearer token with its associated TangleHub bearer source */\nexport interface ResolvedTangleHubBearer {\n bearer: string\n source: TangleHubBearerSource\n}\n\n/** Resolve options required to obtain a user's TangleHub bearer token including environment and API key retrieval */\nexport interface ResolveUserTangleHubBearerOptions {\n userId: string\n /** Deployment context. Only local development may use env credentials. */\n environment?: TangleExecutionEnvironment\n /** Env to read for the local-development bearer. */\n env?: Record<string, string | undefined>\n /** App-owned lookup for the caller's linked platform API key. */\n getUserApiKey: () => string | null | undefined | Promise<string | null | undefined>\n}\n\n/** Resolve options for retrieving a TangleHub bearer token for a specified user */\nexport interface ResolveUserTangleHubBearerForUserOptions<UserId = string> {\n userId: UserId\n environment?: TangleExecutionEnvironment\n env?: Record<string, string | undefined>\n getUserApiKey: (userId: UserId) => string | null | undefined | Promise<string | null | undefined>\n}\n\n/** Represent missing Tangle platform link error for a specified user ID */\nexport class TangleBearerMissingError extends Error {\n constructor(readonly userId: string) {\n super(`No Tangle platform link for user ${userId}`)\n this.name = 'TangleBearerMissingError'\n }\n}\n\n/**\n * Resolve the Tangle bearer used by the integration hub proxy.\n *\n * Local development may use a server env key so apps can exercise the hub\n * without completing cross-site SSO. Deployed contexts must use the caller's\n * linked platform key; this keeps integration ownership aligned with the user.\n */\nexport async function resolveUserTangleHubBearer(\n opts: ResolveUserTangleHubBearerOptions,\n): Promise<ResolvedTangleHubBearer> {\n const resolved = await resolveTangleDevOrUserKey({\n environment: opts.environment,\n env: opts.env,\n getUserApiKey: opts.getUserApiKey,\n })\n if (resolved) return { bearer: resolved.apiKey, source: resolved.source }\n\n throw new TangleBearerMissingError(opts.userId)\n}\n\n/** Resolve the TangleHub bearer token for a specified user based on provided options */\nexport async function resolveUserTangleHubBearerForUser<UserId = string>(\n opts: ResolveUserTangleHubBearerForUserOptions<UserId>,\n): Promise<ResolvedTangleHubBearer> {\n return resolveUserTangleHubBearer({\n userId: String(opts.userId),\n environment: opts.environment,\n env: opts.env,\n getUserApiKey: () => opts.getUserApiKey(opts.userId),\n })\n}\n\n/** Structural guard (name + userId shape) — robust when the error class is\n * constructed in a different module instance than the one checking it. */\nexport function isTangleBearerMissingError(error: unknown): error is TangleBearerMissingError {\n return (\n error instanceof Error &&\n error.name === 'TangleBearerMissingError' &&\n typeof (error as { userId?: unknown }).userId === 'string'\n )\n}\n\n/** Structural detection of the platform hub wire error (name + numeric status). */\nexport function isPlatformHubErrorLike(error: unknown): error is Error & { status: number; code?: string } {\n return (\n error instanceof Error &&\n error.name === 'PlatformHubError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\n/** Structural subset of the platform hub wire client — extra methods are fine. */\nexport interface HubClientLike {\n catalog(): Promise<unknown>\n listConnections(): Promise<unknown>\n revokeConnection(connectionId: string): Promise<unknown>\n startAuth(input: {\n providerId: string\n connectorId: string\n returnUrl: string\n requestedScopes?: string[]\n }): Promise<{ authorizationUrl: string; state: string }>\n listHealthchecks(): Promise<unknown>\n}\n\n/** Define methods to require user ID, get bearer token, and create a hub client bound to the bearer */\nexport interface HubProxyContext {\n /** Resolve the authenticated user id. Throw the app's own auth Response /\n * redirect to reject — it propagates untouched. */\n requireUserId(request: Request): Promise<string>\n /** The user's platform bearer; throw `TangleBearerMissingError` when unlinked. */\n getBearer(userId: string): Promise<string>\n /** A hub client bound to the bearer. */\n createHubClient(bearer: string): HubClientLike\n}\n\n/** Define arguments for configuring a proxy route with request and optional parameters */\nexport interface HubProxyRouteArgs {\n request: Request\n params?: Record<string, string | undefined>\n}\n\n/** Define routes for hub proxy handling catalog, connections, healthchecks, and authorization actions */\nexport interface HubProxyRoutes {\n /** GET → `{ catalog }`. */\n catalog(args: HubProxyRouteArgs): Promise<Response>\n /** GET → `{ connections }`. */\n connections(args: HubProxyRouteArgs): Promise<Response>\n /** DELETE → the platform revocation result verbatim; 405 otherwise. */\n connectionDelete(args: { request: Request; params: { connectionId: string } }): Promise<Response>\n /** GET → `{ healthchecks }`. */\n healthchecks(args: HubProxyRouteArgs): Promise<Response>\n /** POST `{ providerId, connectorId, returnUrl, requestedScopes? }` →\n * `{ authorizationUrl, state }`; 405 non-POST; 400 on bad JSON / missing fields. */\n authStart(args: HubProxyRouteArgs): Promise<Response>\n}\n\ninterface StartAuthBody {\n providerId?: string\n connectorId?: string\n returnUrl?: string\n requestedScopes?: string[]\n}\n\n/** Resolve hub proxy routes with authentication and error handling based on the given context */\nexport function createHubProxyRoutes(ctx: HubProxyContext): HubProxyRoutes {\n /** Auth runs OUTSIDE the proxy try/catch so the app's auth throw (redirect\n * Response etc.) is never swallowed; bearer + platform errors are mapped. */\n async function proxy(request: Request, call: (hub: HubClientLike) => Promise<Response>): Promise<Response> {\n const userId = await ctx.requireUserId(request)\n try {\n const bearer = await ctx.getBearer(userId)\n return await call(ctx.createHubClient(bearer))\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n }\n\n return {\n catalog: ({ request }) => proxy(request, async (hub) => Response.json({ catalog: await hub.catalog() })),\n\n connections: ({ request }) =>\n proxy(request, async (hub) => Response.json({ connections: await hub.listConnections() })),\n\n connectionDelete: async ({ request, params }) => {\n if (request.method !== 'DELETE') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n return proxy(request, async (hub) => Response.json(await hub.revokeConnection(params.connectionId)))\n },\n\n healthchecks: ({ request }) =>\n proxy(request, async (hub) => Response.json({ healthchecks: await hub.listHealthchecks() })),\n\n authStart: async ({ request }) => {\n if (request.method !== 'POST') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n const userId = await ctx.requireUserId(request)\n let body: StartAuthBody\n try {\n body = (await request.json()) as StartAuthBody\n } catch {\n return Response.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n if (!body.providerId || !body.connectorId || !body.returnUrl) {\n return Response.json({ error: 'providerId, connectorId, and returnUrl are required' }, { status: 400 })\n }\n try {\n const bearer = await ctx.getBearer(userId)\n const result = await ctx.createHubClient(bearer).startAuth({\n providerId: body.providerId,\n connectorId: body.connectorId,\n returnUrl: body.returnUrl,\n requestedScopes: body.requestedScopes,\n })\n return Response.json({ authorizationUrl: result.authorizationUrl, state: result.state })\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n },\n }\n}\n","/**\n * Platform billing HTTP transport + tier state for apps on the shared\n * Tangle balance model (id.tangle.tools). Reads authenticate as the user via\n * their per-user platform key (the platform resolves the caller from the\n * key; service or impersonation headers on read routes are rejected). The\n * deduct write authenticates as the product service (`Bearer <serviceToken>`\n * + `X-Service-Name`) and names the target user in the body. Also provides a\n * fetch-backed implementation of the `/billing` module's\n * `PlatformBillingClient` seam (type-only import — no runtime coupling).\n */\n\nimport type { PlatformBillingClient, PlatformIdentity } from '../billing/index'\nimport { resolveTangleExecutionEnvironment } from '../runtime/model'\n\n/** Define available subscription tiers for the TanglePlan service */\nexport type TanglePlanTier = 'free' | 'pro' | 'enterprise'\n\n/** 'pro' | 'enterprise' pass through; anything else (null, unknown) → 'free'. */\nexport function normalizeTanglePlanTier(plan: string | null | undefined): TanglePlanTier {\n return plan === 'pro' || plan === 'enterprise' ? plan : 'free'\n}\n\n/** Represent platform billing HTTP errors with status code and detailed message */\nexport class PlatformBillingHttpError extends Error {\n constructor(\n readonly status: number,\n detail: string,\n ) {\n super(`Platform request failed (${status}): ${detail}`)\n this.name = 'PlatformBillingHttpError'\n }\n}\n\n/** Structural guard (name + numeric status) — robust across module instances. */\nexport function isPlatformBillingHttpError(error: unknown): error is PlatformBillingHttpError {\n return (\n error instanceof Error &&\n error.name === 'PlatformBillingHttpError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\n/** Define HTTP options for platform billing including base URL, service token, product slug, fetch implementation, and timeout */\nexport interface PlatformBillingHttpOptions {\n /** Platform root, e.g. https://id.tangle.tools (trailing slashes stripped). */\n baseUrl: string\n /** Used only by `deduct()`; resolved lazily so reads never require it.\n * Throws at call time when empty. */\n serviceToken: string | (() => string)\n /** Product slug — the `X-Service-Name` header and the deduct `product` field. */\n productSlug: string\n fetchImpl?: typeof fetch\n /** Default 10 000. */\n timeoutMs?: number\n}\n\n/** Describe subscription tier and status information for a platform user */\nexport interface PlatformSubscriptionInfo {\n tier: TanglePlanTier\n status: string | null\n}\n\n/** Describe the platform balance and lifetime spending with an optional update timestamp */\nexport interface PlatformBalanceSnapshot {\n balance: number\n lifetimeSpent: number\n updatedAt?: string\n}\n\n/** Describe a product's usage and spending metrics on the platform */\nexport interface PlatformUsageProductRow {\n product: string | null\n totalSpent: number\n count: number\n}\n\n/** Lifecycle of a per-product seat subscription, mirroring the Stripe states\n * the platform persists. 'none' = the user has never held this seat. */\nexport type SeatStatus = 'none' | 'active' | 'trialing' | 'past_due' | 'canceled'\n\n/** Price and included shared-wallet credit for one seat billing period. */\nexport interface ProductSeatOfferPeriod {\n priceCents: number\n includedCreditsCents: number\n}\n\n/** Commercial terms returned by the platform's product catalog. Products use\n * this exact object for display instead of duplicating prices in UI copy. */\nexport interface ProductSeatOffer {\n currency: 'usd'\n interval: 'month'\n recurring: ProductSeatOfferPeriod\n introductory: ProductSeatOfferPeriod | null\n}\n\n/**\n * Per-product entitlement snapshot from the platform — the single read that\n * tells a product whether to show its workspace or the seat paywall. Shape\n * matches `GET /v1/billing/product-entitlement?product=<id>`.\n *\n * `hasSeat` is computed platform-side from the raw seat row.\n * It is true for an active or trialing seat whose period has not lapsed.\n * `onFreeTier` remains in the wire shape for compatibility, but this client\n * always returns false and never uses it for access.\n */\nexport interface ProductEntitlement {\n seatStatus: SeatStatus\n /** ISO timestamp the active seat's paid period runs until; null when none. */\n currentPeriodEnd: string | null\n /** Cumulative inference spend across the whole suite, in dollars. */\n lifetimeSpentUsd: number\n hasSeat: boolean\n /** Platform access decision, including funded balance and identity policy. */\n paidAccess?: boolean\n onFreeTier: boolean\n /** Present when the platform exposes catalog-backed commercial terms. */\n offer?: ProductSeatOffer\n}\n\nfunction productSeatOffer(value: unknown): ProductSeatOffer | undefined {\n if (!value || typeof value !== 'object') return undefined\n const candidate = value as Record<string, unknown>\n if (candidate.currency !== 'usd' || candidate.interval !== 'month') return undefined\n\n const period = (input: unknown, allowZeroPrice: boolean): ProductSeatOfferPeriod | undefined => {\n if (!input || typeof input !== 'object') return undefined\n const data = input as Record<string, unknown>\n const priceCents = data.priceCents\n const includedCreditsCents = data.includedCreditsCents\n if (\n typeof priceCents !== 'number' ||\n !Number.isSafeInteger(priceCents) ||\n (allowZeroPrice ? priceCents < 0 : priceCents <= 0) ||\n typeof includedCreditsCents !== 'number' ||\n !Number.isSafeInteger(includedCreditsCents) ||\n includedCreditsCents < 0\n ) {\n return undefined\n }\n return { priceCents, includedCreditsCents }\n }\n\n const recurring = period(candidate.recurring, false)\n const introductory =\n candidate.introductory === null\n ? null\n : period(candidate.introductory, true)\n if (!recurring || introductory === undefined) return undefined\n return {\n currency: 'usd',\n interval: 'month',\n recurring,\n introductory,\n }\n}\n\n/** Define methods to interact with platform billing endpoints using user or service authentication */\nexport interface PlatformBillingHttp {\n /** GET /v1/plans/current (user bearer). */\n getSubscription(userApiKey: string): Promise<PlatformSubscriptionInfo>\n /** GET /v1/billing/balance (user bearer). */\n getBalance(userApiKey: string): Promise<PlatformBalanceSnapshot>\n /** GET /v1/billing/usage (user bearer). */\n getUsageByProduct(userApiKey: string): Promise<PlatformUsageProductRow[]>\n /** GET /v1/billing/product-entitlement?product=<id> (user bearer). */\n getProductEntitlement(userApiKey: string, productId: string): Promise<ProductEntitlement>\n /** POST /v1/billing/deduct (service token). */\n deduct(input: {\n platformUserId: string\n amountUsd: number\n type: string\n description: string\n referenceId: string\n }): Promise<void>\n /** Absolute URL of the platform's billing-management surface. */\n billingUrl(): string\n /** Absolute URL of the catalog-backed seat checkout for `productId`. */\n seatCheckoutUrl(productId: string): string\n}\n\n/** Create a PlatformBillingHttp instance configured with given options and default behaviors */\nexport function createPlatformBillingHttp(opts: PlatformBillingHttpOptions): PlatformBillingHttp {\n const baseUrl = opts.baseUrl.replace(/\\/+$/, '')\n if (!baseUrl) throw new Error('PlatformBillingHttpOptions.baseUrl is required')\n if (!opts.productSlug) throw new Error('PlatformBillingHttpOptions.productSlug is required')\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 10_000\n\n function resolveServiceToken(): string {\n const token = typeof opts.serviceToken === 'function' ? opts.serviceToken() : opts.serviceToken\n if (!token) throw new Error('A platform service token is required for deduct')\n return token\n }\n\n async function request<T>(path: string, init: RequestInit, headers: Headers): Promise<T> {\n const res = await fetchImpl(`${baseUrl}${path}`, {\n ...init,\n headers,\n signal: AbortSignal.timeout(timeoutMs),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as { error?: { message?: string } } | null\n throw new PlatformBillingHttpError(res.status, body?.error?.message ?? res.statusText)\n }\n return res.json() as Promise<T>\n }\n\n function userRead<T>(userApiKey: string, path: string): Promise<T> {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${userApiKey}`)\n return request<T>(path, {}, headers)\n }\n\n return {\n async getSubscription(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { subscription?: { plan?: string | null; status?: string | null } | null }\n }>(userApiKey, '/v1/plans/current')\n const sub = body.data?.subscription ?? null\n return { tier: normalizeTanglePlanTier(sub?.plan), status: sub?.status ?? null }\n },\n\n async getBalance(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { balance?: number; lifetimeSpent?: number; updatedAt?: string }\n }>(userApiKey, '/v1/billing/balance')\n return {\n balance: body.data?.balance ?? 0,\n lifetimeSpent: body.data?.lifetimeSpent ?? 0,\n updatedAt: body.data?.updatedAt,\n }\n },\n\n async getUsageByProduct(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: Array<{ product?: string | null; totalSpent?: number; count?: number }>\n }>(userApiKey, '/v1/billing/usage')\n return (body.data ?? []).map((row) => ({\n product: row.product ?? null,\n totalSpent: row.totalSpent ?? 0,\n count: row.count ?? 0,\n }))\n },\n\n async getProductEntitlement(userApiKey, productId) {\n const slug = encodeURIComponent(productId)\n const body = await userRead<{\n success: boolean\n data?: {\n seatStatus?: SeatStatus | null\n currentPeriodEnd?: string | null\n lifetimeSpentUsd?: number | null\n hasSeat?: boolean | null\n paidAccess?: unknown\n onFreeTier?: boolean | null\n offer?: unknown\n }\n }>(userApiKey, `/v1/billing/product-entitlement?product=${slug}`)\n const data = body.data ?? {}\n const hasSeat = data.hasSeat === true\n const offer = productSeatOffer(data.offer)\n return {\n seatStatus: data.seatStatus ?? 'none',\n currentPeriodEnd: data.currentPeriodEnd ?? null,\n lifetimeSpentUsd: data.lifetimeSpentUsd ?? 0,\n hasSeat,\n ...('paidAccess' in data ? { paidAccess: data.paidAccess === true } : {}),\n // Product-funded free access is retired. Ignore stale server signals.\n onFreeTier: false,\n ...(offer ? { offer } : {}),\n }\n },\n\n async deduct(input) {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${resolveServiceToken()}`)\n headers.set('X-Service-Name', opts.productSlug)\n headers.set('Content-Type', 'application/json')\n await request('/v1/billing/deduct', {\n method: 'POST',\n body: JSON.stringify({\n userId: input.platformUserId,\n amount: input.amountUsd,\n type: input.type,\n product: opts.productSlug,\n description: input.description,\n referenceId: input.referenceId,\n }),\n }, headers)\n },\n\n billingUrl() {\n return `${baseUrl}/app/billing`\n },\n\n seatCheckoutUrl(productId) {\n return seatCheckoutUrl(baseUrl, productId)\n },\n }\n}\n\n/**\n * Platform Stripe checkout URL for a product's catalog-backed seat. The\n * platform resolves the product-specific price and introductory offer from the\n * `product` query param. Mirrors the `billingUrl()` shape — a deterministic\n * platform-rooted URL with no client-side pricing decisions.\n */\nexport function seatCheckoutUrl(baseUrl: string, productId: string): string {\n const root = baseUrl.replace(/\\/+$/, '')\n return `${root}/app/billing/seat/checkout?product=${encodeURIComponent(productId)}`\n}\n\n// ── Tier policy + composed state ────────────────────────────────────────────\n\n/** Define policy settings for concurrency and overage allowance in a tangle tier */\nexport interface TangleTierPolicy {\n concurrency: number\n overageAllowed: boolean\n}\n\n/** Define default concurrency and overage policies for each TanglePlanTier level */\nexport const DEFAULT_TANGLE_TIER_POLICY: Record<TanglePlanTier, TangleTierPolicy> = {\n free: { concurrency: 1, overageAllowed: false },\n pro: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n enterprise: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n}\n\n/** Describe the state of a Tangle plan tier including subscription, balance, spending, and concurrency details */\nexport interface TangleTierState {\n tier: TanglePlanTier\n subscriptionStatus: string | null\n remainingBalanceUsd: number\n lifetimeSpentUsd: number\n concurrency: number\n overageAllowed: boolean\n}\n\n/**\n * Read subscription + balance and project them onto the tier policy. A\n * null/absent key fails CLOSED (free tier, zero balance) — a billable run is\n * never started against an unknown balance. Platform errors throw; callers\n * on the billable path choose their posture explicitly.\n */\nexport async function readTangleTierState(\n http: PlatformBillingHttp,\n userApiKey: string | null | undefined,\n policy: Record<TanglePlanTier, TangleTierPolicy> = DEFAULT_TANGLE_TIER_POLICY,\n): Promise<TangleTierState> {\n if (!userApiKey) {\n return {\n tier: 'free',\n subscriptionStatus: null,\n remainingBalanceUsd: 0,\n lifetimeSpentUsd: 0,\n ...policy.free,\n }\n }\n const [subscription, balance] = await Promise.all([\n http.getSubscription(userApiKey),\n http.getBalance(userApiKey),\n ])\n return {\n tier: subscription.tier,\n subscriptionStatus: subscription.status,\n remainingBalanceUsd: balance.balance,\n lifetimeSpentUsd: balance.lifetimeSpent,\n ...policy[subscription.tier],\n }\n}\n\n// ── Per-product seat entitlement ────────────────────────────────────────────\n\n/** Product-funded free inference spend is disabled. */\nexport const FREE_TIER_SPEND_CAP_USD = 0\n\n/**\n * Default name of the per-app feature flag that controls seat billing.\n * Explicit values override the environment default.\n */\nexport const DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR = 'SEAT_BILLING_ENABLED'\n\n/** Define options to configure seat billing flag environment variables and override flag name */\nexport interface SeatBillingFlagOptions {\n env?: Record<string, string | undefined>\n /** Override the flag name; default {@link DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR}. */\n flagEnvVar?: string\n}\n\n/**\n * An explicit flag controls seat billing in every environment.\n * Without a flag, known development and test environments default to OFF.\n * Deployed and unknown environments default to ON.\n */\nexport function isSeatBillingEnabled(opts: SeatBillingFlagOptions = {}): boolean {\n const env =\n opts.env ??\n (typeof process !== 'undefined' ? (process.env as Record<string, string | undefined>) : undefined)\n if (!env) return true\n const flag = env[opts.flagEnvVar ?? DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR]?.trim().toLowerCase()\n if (flag) return !['false', '0', 'off', 'disabled', 'no'].includes(flag)\n const environment = resolveTangleExecutionEnvironment(env)\n return environment !== 'development' && environment !== 'test'\n}\n\n/**\n * Read a user's entitlement for one product.\n * An absent key or disabled flag returns no access.\n * Transport failures propagate so the product can return a retryable error.\n *\n * @param flag — pass {@link isSeatBillingEnabled} (or your own boolean) so the\n * product owns when the gate engages. When false, no network call is made.\n */\nexport async function getProductEntitlement(\n http: Pick<PlatformBillingHttp, 'getProductEntitlement'>,\n userApiKey: string | null | undefined,\n productId: string,\n flag = true,\n): Promise<ProductEntitlement> {\n if (!flag) return unavailableEntitlement()\n if (!userApiKey) return unavailableEntitlement()\n return http.getProductEntitlement(userApiKey, productId)\n}\n\nfunction unavailableEntitlement(): ProductEntitlement {\n return {\n seatStatus: 'none',\n currentPeriodEnd: null,\n lifetimeSpentUsd: 0,\n hasSeat: false,\n onFreeTier: false,\n }\n}\n\n/** Honor platform access policy; older endpoints provide only seat authority. */\nexport function isProductEntitled(ent: ProductEntitlement): boolean {\n return ent.paidAccess === undefined ? ent.hasSeat === true : ent.paidAccess === true\n}\n\n// ── Bridge onto the /billing seam ───────────────────────────────────────────\n\n/** Define a contract for resolving platform identities based on user identifiers */\nexport interface PlatformIdentityStore {\n resolveIdentity(userId: string): Promise<PlatformIdentity | null>\n}\n\n/** Concrete fetch-backed `PlatformBillingClient<TanglePlanTier>` for\n * `createPlatformBalanceManager` (from `/billing`). */\nexport function createTanglePlatformBillingClient(\n http: PlatformBillingHttp,\n identity: PlatformIdentityStore,\n): PlatformBillingClient<TanglePlanTier> {\n return {\n resolveIdentity: (userId) => identity.resolveIdentity(userId),\n getPlan: async (apiKey) => (await http.getSubscription(apiKey)).tier,\n getBalance: async (apiKey) => {\n const snapshot = await http.getBalance(apiKey)\n return { balance: snapshot.balance, lifetimeSpent: snapshot.lifetimeSpent }\n },\n getUsageByProduct: (apiKey) => http.getUsageByProduct(apiKey),\n deduct: (input) => http.deduct(input),\n }\n}\n","/** Verified key fields required to enter a product's private API routes. */\nexport interface RequestApiKey {\n keyId: string\n ownerId: string\n scopes: readonly string[]\n /** Unix epoch milliseconds. Private API credentials must expire. */\n expiresAt: number\n}\n\nexport interface ApiKeyRequestAuthOptions<Key extends RequestApiKey, Identity> {\n /** Verify against the existing key store, including revocation and spending limits. */\n verify(authorization: string): Promise<Key | null>\n /** Return one scope or all required scopes; null or an empty list denies the route. */\n requiredScope(request: Request): string | readonly string[] | null\n /** Load the owner from product storage; retain normal workspace and tenant authorization. */\n resolveIdentity(key: Key): Promise<Identity | null>\n /** Atomically enforce the existing key's request quotas before admitting the request. */\n claimRequest(key: Key, requestId: string): Promise<{ allowed: boolean; retryAfterSeconds?: number }>\n}\n\n/**\n * Adapt existing Bearer keys to private product routes without issuing sessions.\n * Only absent authorization returns null; supplied invalid credentials never\n * fall through to cookie authentication. Callers retain their ordinary RBAC.\n */\nexport function createApiKeyRequestAuth<Key extends RequestApiKey, Identity>(\n options: ApiKeyRequestAuthOptions<Key, Identity>,\n): (request: Request) => Promise<Identity | null> {\n return async (request) => {\n const authorization = request.headers.get('Authorization')\n if (authorization === null) return null\n const bearer = /^Bearer +(\\S+)$/i.exec(authorization)\n if (!bearer) {\n throw denied(401, 'api_key.invalid', 'Invalid API key')\n }\n const requiredScope = options.requiredScope(request)\n const requiredScopes = typeof requiredScope === 'string' ? [requiredScope] : Array.from(requiredScope ?? [])\n if (!requiredScopes?.length || !requiredScopes.every(scope => typeof scope === 'string' && scope.trim())) {\n throw denied(403, 'api_key.route_denied', 'API key access is not enabled for this route')\n }\n const key = await options.verify(`Bearer ${bearer[1]}`)\n if (!key || !key.keyId?.trim() || !key.ownerId?.trim()) {\n throw denied(401, 'api_key.invalid', 'Invalid API key')\n }\n if (!Number.isSafeInteger(key.expiresAt) || key.expiresAt <= Date.now()) {\n throw denied(401, 'api_key.expired', 'An unexpired API key with a finite expiry is required')\n }\n if (!Array.isArray(key.scopes) || !requiredScopes.every(scope => key.scopes.includes(scope))) {\n throw denied(403, 'api_key.insufficient_scope', `API key requires scopes: ${requiredScopes.join(', ')}`)\n }\n const identity = await options.resolveIdentity(key)\n if (!identity) throw denied(401, 'api_key.invalid_owner', 'API key owner is unavailable')\n const claim = await options.claimRequest(key, crypto.randomUUID())\n if (claim.allowed !== true) {\n const headers = new Headers()\n if (Number.isFinite(claim.retryAfterSeconds) && claim.retryAfterSeconds! > 0) {\n headers.set('Retry-After', String(Math.ceil(claim.retryAfterSeconds!)))\n }\n throw denied(429, 'api_key.request_limit_exceeded', 'API key request limit exceeded', headers)\n }\n if (key.expiresAt <= Date.now()) {\n throw denied(401, 'api_key.expired', 'API key expired during authorization')\n }\n return identity\n }\n}\n\nfunction denied(status: number, code: string, error: string, headers?: Headers): Response {\n return Response.json({ error, code }, { status, headers })\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/platform/hub.ts","../../src/platform/billing.ts","../../src/platform/api-key-auth.ts"],"sourcesContent":["/**\n * Integrations-hub proxy routes: the app-side surface that forwards an\n * authenticated user's requests to the platform's `/v1/integrations/*` API\n * using their stored platform key. Auth, key lookup, and the wire client are\n * structural seams (`HubProxyContext`); error detection is by name + shape so\n * it survives bundlers duplicating module instances.\n */\n\nimport {\n resolveTangleDevOrUserKey,\n type TangleExecutionEnvironment,\n type TangleExecutionKeySource,\n} from '../runtime/model'\n\n/** Hub bearer provenance mirrors the execution-key source union. */\nexport type TangleHubBearerSource = TangleExecutionKeySource\n\n/** Represent a resolved bearer token with its associated TangleHub bearer source */\nexport interface ResolvedTangleHubBearer {\n bearer: string\n source: TangleHubBearerSource\n}\n\n/** Resolve options required to obtain a user's TangleHub bearer token including environment and API key retrieval */\nexport interface ResolveUserTangleHubBearerOptions {\n userId: string\n /** Deployment context. Only local development may use env credentials. */\n environment?: TangleExecutionEnvironment\n /** Env to read for the local-development bearer. */\n env?: Record<string, string | undefined>\n /** App-owned lookup for the caller's linked platform API key. */\n getUserApiKey: () => string | null | undefined | Promise<string | null | undefined>\n}\n\n/** Resolve options for retrieving a TangleHub bearer token for a specified user */\nexport interface ResolveUserTangleHubBearerForUserOptions<UserId = string> {\n userId: UserId\n environment?: TangleExecutionEnvironment\n env?: Record<string, string | undefined>\n getUserApiKey: (userId: UserId) => string | null | undefined | Promise<string | null | undefined>\n}\n\n/** Represent missing Tangle platform link error for a specified user ID */\nexport class TangleBearerMissingError extends Error {\n constructor(readonly userId: string) {\n super(`No Tangle platform link for user ${userId}`)\n this.name = 'TangleBearerMissingError'\n }\n}\n\n/**\n * Resolve the Tangle bearer used by the integration hub proxy.\n *\n * Local development may use a server env key so apps can exercise the hub\n * without completing cross-site SSO. Deployed contexts must use the caller's\n * linked platform key; this keeps integration ownership aligned with the user.\n */\nexport async function resolveUserTangleHubBearer(\n opts: ResolveUserTangleHubBearerOptions,\n): Promise<ResolvedTangleHubBearer> {\n const resolved = await resolveTangleDevOrUserKey({\n environment: opts.environment,\n env: opts.env,\n getUserApiKey: opts.getUserApiKey,\n })\n if (resolved) return { bearer: resolved.apiKey, source: resolved.source }\n\n throw new TangleBearerMissingError(opts.userId)\n}\n\n/** Resolve the TangleHub bearer token for a specified user based on provided options */\nexport async function resolveUserTangleHubBearerForUser<UserId = string>(\n opts: ResolveUserTangleHubBearerForUserOptions<UserId>,\n): Promise<ResolvedTangleHubBearer> {\n return resolveUserTangleHubBearer({\n userId: String(opts.userId),\n environment: opts.environment,\n env: opts.env,\n getUserApiKey: () => opts.getUserApiKey(opts.userId),\n })\n}\n\n/** Structural guard (name + userId shape) — robust when the error class is\n * constructed in a different module instance than the one checking it. */\nexport function isTangleBearerMissingError(error: unknown): error is TangleBearerMissingError {\n return (\n error instanceof Error &&\n error.name === 'TangleBearerMissingError' &&\n typeof (error as { userId?: unknown }).userId === 'string'\n )\n}\n\n/** Structural detection of the platform hub wire error (name + numeric status). */\nexport function isPlatformHubErrorLike(error: unknown): error is Error & { status: number; code?: string } {\n return (\n error instanceof Error &&\n error.name === 'PlatformHubError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\n/** Structural subset of the platform hub wire client — extra methods are fine. */\nexport interface HubClientLike {\n catalog(): Promise<unknown>\n listConnections(): Promise<unknown>\n revokeConnection(connectionId: string): Promise<unknown>\n startAuth(input: {\n providerId: string\n connectorId: string\n returnUrl: string\n requestedScopes?: string[]\n }): Promise<{ authorizationUrl: string; state: string }>\n listHealthchecks(): Promise<unknown>\n}\n\n/** Define methods to require user ID, get bearer token, and create a hub client bound to the bearer */\nexport interface HubProxyContext {\n /** Resolve the authenticated user id. Throw the app's own auth Response /\n * redirect to reject — it propagates untouched. */\n requireUserId(request: Request): Promise<string>\n /** The user's platform bearer; throw `TangleBearerMissingError` when unlinked. */\n getBearer(userId: string): Promise<string>\n /** A hub client bound to the bearer. */\n createHubClient(bearer: string): HubClientLike\n}\n\n/** Define arguments for configuring a proxy route with request and optional parameters */\nexport interface HubProxyRouteArgs {\n request: Request\n params?: Record<string, string | undefined>\n}\n\n/** Define routes for hub proxy handling catalog, connections, healthchecks, and authorization actions */\nexport interface HubProxyRoutes {\n /** GET → `{ catalog }`. */\n catalog(args: HubProxyRouteArgs): Promise<Response>\n /** GET → `{ connections }`. */\n connections(args: HubProxyRouteArgs): Promise<Response>\n /** DELETE → the platform revocation result verbatim; 405 otherwise. */\n connectionDelete(args: { request: Request; params: { connectionId: string } }): Promise<Response>\n /** GET → `{ healthchecks }`. */\n healthchecks(args: HubProxyRouteArgs): Promise<Response>\n /** POST `{ providerId, connectorId, returnUrl, requestedScopes? }` →\n * `{ authorizationUrl, state }`; 405 non-POST; 400 on bad JSON / missing fields. */\n authStart(args: HubProxyRouteArgs): Promise<Response>\n}\n\ninterface StartAuthBody {\n providerId?: string\n connectorId?: string\n returnUrl?: string\n requestedScopes?: string[]\n}\n\n/** Resolve hub proxy routes with authentication and error handling based on the given context */\nexport function createHubProxyRoutes(ctx: HubProxyContext): HubProxyRoutes {\n /** Auth runs OUTSIDE the proxy try/catch so the app's auth throw (redirect\n * Response etc.) is never swallowed; bearer + platform errors are mapped. */\n async function proxy(request: Request, call: (hub: HubClientLike) => Promise<Response>): Promise<Response> {\n const userId = await ctx.requireUserId(request)\n try {\n const bearer = await ctx.getBearer(userId)\n return await call(ctx.createHubClient(bearer))\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n }\n\n return {\n catalog: ({ request }) => proxy(request, async (hub) => Response.json({ catalog: await hub.catalog() })),\n\n connections: ({ request }) =>\n proxy(request, async (hub) => Response.json({ connections: await hub.listConnections() })),\n\n connectionDelete: async ({ request, params }) => {\n if (request.method !== 'DELETE') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n return proxy(request, async (hub) => Response.json(await hub.revokeConnection(params.connectionId)))\n },\n\n healthchecks: ({ request }) =>\n proxy(request, async (hub) => Response.json({ healthchecks: await hub.listHealthchecks() })),\n\n authStart: async ({ request }) => {\n if (request.method !== 'POST') {\n return Response.json({ error: 'Method not allowed' }, { status: 405 })\n }\n const userId = await ctx.requireUserId(request)\n let body: StartAuthBody\n try {\n body = (await request.json()) as StartAuthBody\n } catch {\n return Response.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n if (!body.providerId || !body.connectorId || !body.returnUrl) {\n return Response.json({ error: 'providerId, connectorId, and returnUrl are required' }, { status: 400 })\n }\n try {\n const bearer = await ctx.getBearer(userId)\n const result = await ctx.createHubClient(bearer).startAuth({\n providerId: body.providerId,\n connectorId: body.connectorId,\n returnUrl: body.returnUrl,\n requestedScopes: body.requestedScopes,\n })\n return Response.json({ authorizationUrl: result.authorizationUrl, state: result.state })\n } catch (err) {\n if (isTangleBearerMissingError(err)) {\n return Response.json({ error: 'tangle_link_required' }, { status: 412 })\n }\n if (isPlatformHubErrorLike(err)) {\n return Response.json({ error: err.message, code: err.code }, { status: err.status })\n }\n throw err\n }\n },\n }\n}\n","/**\n * Platform billing HTTP transport + tier state for apps on the shared\n * Tangle balance model (id.tangle.tools). Reads authenticate as the user via\n * their per-user platform key (the platform resolves the caller from the\n * key; service or impersonation headers on read routes are rejected). The\n * deduct write authenticates as the product service (`Bearer <serviceToken>`\n * + `X-Service-Name`) and names the target user in the body. Also provides a\n * fetch-backed implementation of the `/billing` module's\n * `PlatformBillingClient` seam (type-only import — no runtime coupling).\n */\n\nimport type { PlatformBillingClient, PlatformIdentity } from '../billing/index'\nimport { resolveTangleExecutionEnvironment } from '../runtime/model'\n\n/** Define available subscription tiers for the TanglePlan service */\nexport type TanglePlanTier = 'free' | 'pro' | 'enterprise'\n\n/** 'pro' | 'enterprise' pass through; anything else (null, unknown) → 'free'. */\nexport function normalizeTanglePlanTier(plan: string | null | undefined): TanglePlanTier {\n return plan === 'pro' || plan === 'enterprise' ? plan : 'free'\n}\n\n/** Represent platform billing HTTP errors with status code and detailed message */\nexport class PlatformBillingHttpError extends Error {\n constructor(\n readonly status: number,\n detail: string,\n ) {\n super(`Platform request failed (${status}): ${detail}`)\n this.name = 'PlatformBillingHttpError'\n }\n}\n\n/** Structural guard (name + numeric status) — robust across module instances. */\nexport function isPlatformBillingHttpError(error: unknown): error is PlatformBillingHttpError {\n return (\n error instanceof Error &&\n error.name === 'PlatformBillingHttpError' &&\n typeof (error as { status?: unknown }).status === 'number'\n )\n}\n\n/** Define HTTP options for platform billing including base URL, service token, product slug, fetch implementation, and timeout */\nexport interface PlatformBillingHttpOptions {\n /** Platform root, e.g. https://id.tangle.tools (trailing slashes stripped). */\n baseUrl: string\n /** Used only by `deduct()`; resolved lazily so reads never require it.\n * Throws at call time when empty. */\n serviceToken: string | (() => string)\n /** Product slug — the `X-Service-Name` header and the deduct `product` field. */\n productSlug: string\n fetchImpl?: typeof fetch\n /** Default 10 000. */\n timeoutMs?: number\n}\n\n/** Describe subscription tier and status information for a platform user */\nexport interface PlatformSubscriptionInfo {\n tier: TanglePlanTier\n status: string | null\n}\n\n/** Describe the platform balance and lifetime spending with an optional update timestamp */\nexport interface PlatformBalanceSnapshot {\n balance: number\n lifetimeSpent: number\n updatedAt?: string\n}\n\n/** Describe a product's usage and spending metrics on the platform */\nexport interface PlatformUsageProductRow {\n product: string | null\n totalSpent: number\n count: number\n}\n\n/** Lifecycle of a per-product seat subscription, mirroring the Stripe states\n * the platform persists. 'none' = the user has never held this seat. */\nexport type SeatStatus = 'none' | 'active' | 'trialing' | 'past_due' | 'canceled'\n\n/** Price and included shared-wallet credit for one seat billing period. */\nexport interface ProductSeatOfferPeriod {\n priceCents: number\n includedCreditsCents: number\n}\n\n/** Commercial terms returned by the platform's product catalog. Products use\n * this exact object for display instead of duplicating prices in UI copy. */\nexport interface ProductSeatOffer {\n currency: 'usd'\n interval: 'month'\n recurring: ProductSeatOfferPeriod\n introductory: ProductSeatOfferPeriod | null\n}\n\n/**\n * Per-product entitlement snapshot from the platform — the single read that\n * tells a product whether to show its workspace or the seat paywall. Shape\n * matches `GET /v1/billing/product-entitlement?product=<id>`.\n *\n * `hasSeat` is computed platform-side from the raw seat row.\n * It is true for an active or trialing seat whose period has not lapsed.\n * `onFreeTier` remains in the wire shape for compatibility, but this client\n * always returns false and never uses it for access.\n */\nexport interface ProductEntitlement {\n seatStatus: SeatStatus\n /** ISO timestamp the active seat's paid period runs until; null when none. */\n currentPeriodEnd: string | null\n /** Cumulative inference spend across the whole suite, in dollars. */\n lifetimeSpentUsd: number\n hasSeat: boolean\n /** Platform access decision, including funded balance and identity policy. */\n paidAccess?: boolean\n onFreeTier: boolean\n /** Present when the platform exposes catalog-backed commercial terms. */\n offer?: ProductSeatOffer\n}\n\nfunction productSeatOffer(value: unknown): ProductSeatOffer | undefined {\n if (!value || typeof value !== 'object') return undefined\n const candidate = value as Record<string, unknown>\n if (candidate.currency !== 'usd' || candidate.interval !== 'month') return undefined\n\n const period = (input: unknown, allowZeroPrice: boolean): ProductSeatOfferPeriod | undefined => {\n if (!input || typeof input !== 'object') return undefined\n const data = input as Record<string, unknown>\n const priceCents = data.priceCents\n const includedCreditsCents = data.includedCreditsCents\n if (\n typeof priceCents !== 'number' ||\n !Number.isSafeInteger(priceCents) ||\n (allowZeroPrice ? priceCents < 0 : priceCents <= 0) ||\n typeof includedCreditsCents !== 'number' ||\n !Number.isSafeInteger(includedCreditsCents) ||\n includedCreditsCents < 0\n ) {\n return undefined\n }\n return { priceCents, includedCreditsCents }\n }\n\n const recurring = period(candidate.recurring, false)\n const introductory =\n candidate.introductory === null\n ? null\n : period(candidate.introductory, true)\n if (!recurring || introductory === undefined) return undefined\n return {\n currency: 'usd',\n interval: 'month',\n recurring,\n introductory,\n }\n}\n\n/** Define methods to interact with platform billing endpoints using user or service authentication */\nexport interface PlatformBillingHttp {\n /** GET /v1/plans/current (user bearer). */\n getSubscription(userApiKey: string): Promise<PlatformSubscriptionInfo>\n /** GET /v1/billing/balance (user bearer). */\n getBalance(userApiKey: string): Promise<PlatformBalanceSnapshot>\n /** GET /v1/billing/usage (user bearer). */\n getUsageByProduct(userApiKey: string): Promise<PlatformUsageProductRow[]>\n /** GET /v1/billing/product-entitlement?product=<id> (user bearer). */\n getProductEntitlement(userApiKey: string, productId: string): Promise<ProductEntitlement>\n /** POST /v1/billing/deduct (service token). */\n deduct(input: {\n platformUserId: string\n amountUsd: number\n type: string\n description: string\n referenceId: string\n }): Promise<void>\n /** Absolute URL of the platform's billing-management surface. */\n billingUrl(): string\n /** Absolute URL of the catalog-backed seat checkout for `productId`. */\n seatCheckoutUrl(productId: string): string\n}\n\n/** Create a PlatformBillingHttp instance configured with given options and default behaviors */\nexport function createPlatformBillingHttp(opts: PlatformBillingHttpOptions): PlatformBillingHttp {\n const baseUrl = opts.baseUrl.replace(/\\/+$/, '')\n if (!baseUrl) throw new Error('PlatformBillingHttpOptions.baseUrl is required')\n if (!opts.productSlug) throw new Error('PlatformBillingHttpOptions.productSlug is required')\n const fetchImpl = opts.fetchImpl ?? fetch\n const timeoutMs = opts.timeoutMs ?? 10_000\n\n function resolveServiceToken(): string {\n const token = typeof opts.serviceToken === 'function' ? opts.serviceToken() : opts.serviceToken\n if (!token) throw new Error('A platform service token is required for deduct')\n return token\n }\n\n async function request<T>(path: string, init: RequestInit, headers: Headers): Promise<T> {\n const res = await fetchImpl(`${baseUrl}${path}`, {\n ...init,\n headers,\n signal: AbortSignal.timeout(timeoutMs),\n })\n if (!res.ok) {\n const body = (await res.json().catch(() => null)) as { error?: { message?: string } } | null\n throw new PlatformBillingHttpError(res.status, body?.error?.message ?? res.statusText)\n }\n return res.json() as Promise<T>\n }\n\n function userRead<T>(userApiKey: string, path: string): Promise<T> {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${userApiKey}`)\n return request<T>(path, {}, headers)\n }\n\n return {\n async getSubscription(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { subscription?: { plan?: string | null; status?: string | null } | null }\n }>(userApiKey, '/v1/plans/current')\n const sub = body.data?.subscription ?? null\n return { tier: normalizeTanglePlanTier(sub?.plan), status: sub?.status ?? null }\n },\n\n async getBalance(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: { balance?: number; lifetimeSpent?: number; updatedAt?: string }\n }>(userApiKey, '/v1/billing/balance')\n return {\n balance: body.data?.balance ?? 0,\n lifetimeSpent: body.data?.lifetimeSpent ?? 0,\n updatedAt: body.data?.updatedAt,\n }\n },\n\n async getUsageByProduct(userApiKey) {\n const body = await userRead<{\n success: boolean\n data?: Array<{ product?: string | null; totalSpent?: number; count?: number }>\n }>(userApiKey, '/v1/billing/usage')\n return (body.data ?? []).map((row) => ({\n product: row.product ?? null,\n totalSpent: row.totalSpent ?? 0,\n count: row.count ?? 0,\n }))\n },\n\n async getProductEntitlement(userApiKey, productId) {\n const slug = encodeURIComponent(productId)\n const body = await userRead<{\n success: boolean\n data?: {\n seatStatus?: SeatStatus | null\n currentPeriodEnd?: string | null\n lifetimeSpentUsd?: number | null\n hasSeat?: boolean | null\n paidAccess?: unknown\n onFreeTier?: boolean | null\n offer?: unknown\n }\n }>(userApiKey, `/v1/billing/product-entitlement?product=${slug}`)\n const data = body.data ?? {}\n const hasSeat = data.hasSeat === true\n const offer = productSeatOffer(data.offer)\n return {\n seatStatus: data.seatStatus ?? 'none',\n currentPeriodEnd: data.currentPeriodEnd ?? null,\n lifetimeSpentUsd: data.lifetimeSpentUsd ?? 0,\n hasSeat,\n ...('paidAccess' in data ? { paidAccess: data.paidAccess === true } : {}),\n // Product-funded free access is retired. Ignore stale server signals.\n onFreeTier: false,\n ...(offer ? { offer } : {}),\n }\n },\n\n async deduct(input) {\n const headers = new Headers()\n headers.set('Authorization', `Bearer ${resolveServiceToken()}`)\n headers.set('X-Service-Name', opts.productSlug)\n headers.set('Content-Type', 'application/json')\n await request('/v1/billing/deduct', {\n method: 'POST',\n body: JSON.stringify({\n userId: input.platformUserId,\n amount: input.amountUsd,\n type: input.type,\n product: opts.productSlug,\n description: input.description,\n referenceId: input.referenceId,\n }),\n }, headers)\n },\n\n billingUrl() {\n return `${baseUrl}/app/billing`\n },\n\n seatCheckoutUrl(productId) {\n return seatCheckoutUrl(baseUrl, productId)\n },\n }\n}\n\n/**\n * Platform Stripe checkout URL for a product's catalog-backed seat. The\n * platform resolves the product-specific price and introductory offer from the\n * `product` query param. Mirrors the `billingUrl()` shape — a deterministic\n * platform-rooted URL with no client-side pricing decisions.\n */\nexport function seatCheckoutUrl(baseUrl: string, productId: string): string {\n const root = baseUrl.replace(/\\/+$/, '')\n return `${root}/app/billing/seat/checkout?product=${encodeURIComponent(productId)}`\n}\n\n// ── Tier policy + composed state ────────────────────────────────────────────\n\n/** Define policy settings for concurrency and overage allowance in a tangle tier */\nexport interface TangleTierPolicy {\n concurrency: number\n overageAllowed: boolean\n}\n\n/** Define default concurrency and overage policies for each TanglePlanTier level */\nexport const DEFAULT_TANGLE_TIER_POLICY: Record<TanglePlanTier, TangleTierPolicy> = {\n free: { concurrency: 1, overageAllowed: false },\n pro: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n enterprise: { concurrency: Number.POSITIVE_INFINITY, overageAllowed: true },\n}\n\n/** Describe the state of a Tangle plan tier including subscription, balance, spending, and concurrency details */\nexport interface TangleTierState {\n tier: TanglePlanTier\n subscriptionStatus: string | null\n remainingBalanceUsd: number\n lifetimeSpentUsd: number\n concurrency: number\n overageAllowed: boolean\n}\n\n/**\n * Read subscription + balance and project them onto the tier policy. A\n * null/absent key fails CLOSED (free tier, zero balance) — a billable run is\n * never started against an unknown balance. Platform errors throw; callers\n * on the billable path choose their posture explicitly.\n */\nexport async function readTangleTierState(\n http: PlatformBillingHttp,\n userApiKey: string | null | undefined,\n policy: Record<TanglePlanTier, TangleTierPolicy> = DEFAULT_TANGLE_TIER_POLICY,\n): Promise<TangleTierState> {\n if (!userApiKey) {\n return {\n tier: 'free',\n subscriptionStatus: null,\n remainingBalanceUsd: 0,\n lifetimeSpentUsd: 0,\n ...policy.free,\n }\n }\n const [subscription, balance] = await Promise.all([\n http.getSubscription(userApiKey),\n http.getBalance(userApiKey),\n ])\n return {\n tier: subscription.tier,\n subscriptionStatus: subscription.status,\n remainingBalanceUsd: balance.balance,\n lifetimeSpentUsd: balance.lifetimeSpent,\n ...policy[subscription.tier],\n }\n}\n\n// ── Per-product seat entitlement ────────────────────────────────────────────\n\n/** Product-funded free inference spend is disabled. */\nexport const FREE_TIER_SPEND_CAP_USD = 0\n\n/**\n * Default name of the per-app feature flag that controls seat billing.\n * Explicit values override the environment default.\n */\nexport const DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR = 'SEAT_BILLING_ENABLED'\n\n/** Define options to configure seat billing flag environment variables and override flag name */\nexport interface SeatBillingFlagOptions {\n env?: Record<string, string | undefined>\n /** Override the flag name; default {@link DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR}. */\n flagEnvVar?: string\n}\n\n/**\n * An explicit flag controls seat billing in every environment.\n * Without a flag, known development and test environments default to OFF.\n * Deployed and unknown environments default to ON.\n */\nexport function isSeatBillingEnabled(opts: SeatBillingFlagOptions = {}): boolean {\n const env =\n opts.env ??\n (typeof process !== 'undefined' ? (process.env as Record<string, string | undefined>) : undefined)\n if (!env) return true\n const flag = env[opts.flagEnvVar ?? DEFAULT_SEAT_BILLING_ENABLED_ENV_VAR]?.trim().toLowerCase()\n if (flag) return !['false', '0', 'off', 'disabled', 'no'].includes(flag)\n const environment = resolveTangleExecutionEnvironment(env)\n return environment !== 'development' && environment !== 'test'\n}\n\n/**\n * Read a user's entitlement for one product.\n * An absent key or disabled flag returns no access.\n * Transport failures propagate so the product can return a retryable error.\n *\n * @param flag — pass {@link isSeatBillingEnabled} (or your own boolean) so the\n * product owns when the gate engages. When false, no network call is made.\n */\nexport async function getProductEntitlement(\n http: Pick<PlatformBillingHttp, 'getProductEntitlement'>,\n userApiKey: string | null | undefined,\n productId: string,\n flag = true,\n): Promise<ProductEntitlement> {\n if (!flag) return unavailableEntitlement()\n if (!userApiKey) return unavailableEntitlement()\n return http.getProductEntitlement(userApiKey, productId)\n}\n\nfunction unavailableEntitlement(): ProductEntitlement {\n return {\n seatStatus: 'none',\n currentPeriodEnd: null,\n lifetimeSpentUsd: 0,\n hasSeat: false,\n onFreeTier: false,\n }\n}\n\n/** Honor platform access policy; older endpoints provide only seat authority. */\nexport function isProductEntitled(ent: ProductEntitlement): boolean {\n return ent.paidAccess === undefined ? ent.hasSeat === true : ent.paidAccess === true\n}\n\n// ── Bridge onto the /billing seam ───────────────────────────────────────────\n\n/** Define a contract for resolving platform identities based on user identifiers */\nexport interface PlatformIdentityStore {\n resolveIdentity(userId: string): Promise<PlatformIdentity | null>\n}\n\n/** Concrete fetch-backed `PlatformBillingClient<TanglePlanTier>` for\n * `createPlatformBalanceManager` (from `/billing`). */\nexport function createTanglePlatformBillingClient(\n http: PlatformBillingHttp,\n identity: PlatformIdentityStore,\n): PlatformBillingClient<TanglePlanTier> {\n return {\n resolveIdentity: (userId) => identity.resolveIdentity(userId),\n getPlan: async (apiKey) => (await http.getSubscription(apiKey)).tier,\n getBalance: async (apiKey) => {\n const snapshot = await http.getBalance(apiKey)\n return { balance: snapshot.balance, lifetimeSpent: snapshot.lifetimeSpent }\n },\n getUsageByProduct: (apiKey) => http.getUsageByProduct(apiKey),\n deduct: (input) => http.deduct(input),\n }\n}\n","/** Verified key fields required to enter a product's private API routes. */\nexport interface RequestApiKey {\n keyId: string\n ownerId: string\n scopes: readonly string[]\n /** Unix epoch milliseconds. Private API credentials must expire. */\n expiresAt: number\n}\n\nexport interface ApiKeyRequestAuthOptions<Key extends RequestApiKey, Identity> {\n /** Verify against the existing key store, including revocation and spending limits. */\n verify(authorization: string): Promise<Key | null>\n /** Return one scope or all required scopes; null or an empty list denies the route. */\n requiredScope(request: Request): string | readonly string[] | null\n /** Load the owner from product storage; retain normal workspace and tenant authorization. */\n resolveIdentity(key: Key): Promise<Identity | null>\n /** Atomically enforce the existing key's request quotas before admitting the request. */\n claimRequest(key: Key, requestId: string): Promise<{ allowed: boolean; retryAfterSeconds?: number }>\n}\n\n/**\n * Adapt existing Bearer keys to private product routes without issuing sessions.\n * Only absent authorization returns null; supplied invalid credentials never\n * fall through to cookie authentication. Callers retain their ordinary RBAC.\n */\nexport function createApiKeyRequestAuth<Key extends RequestApiKey, Identity>(\n options: ApiKeyRequestAuthOptions<Key, Identity>,\n): (request: Request) => Promise<Identity | null> {\n return async (request) => {\n const authorization = request.headers.get('Authorization')\n if (authorization === null) return null\n const bearer = /^Bearer +(\\S+)$/i.exec(authorization)\n if (!bearer) {\n throw denied(401, 'api_key.invalid', 'Invalid API key')\n }\n const requiredScope = options.requiredScope(request)\n const requiredScopes = typeof requiredScope === 'string' ? [requiredScope] : Array.from(requiredScope ?? [])\n if (!requiredScopes?.length || !requiredScopes.every(scope => typeof scope === 'string' && scope.trim())) {\n throw denied(403, 'api_key.route_denied', 'API key access is not enabled for this route')\n }\n const key = await options.verify(`Bearer ${bearer[1]}`)\n if (!key || !key.keyId?.trim() || !key.ownerId?.trim()) {\n throw denied(401, 'api_key.invalid', 'Invalid API key')\n }\n if (!Number.isSafeInteger(key.expiresAt) || key.expiresAt <= Date.now()) {\n throw denied(401, 'api_key.expired', 'An unexpired API key with a finite expiry is required')\n }\n if (!Array.isArray(key.scopes) || !requiredScopes.every(scope => key.scopes.includes(scope))) {\n throw denied(403, 'api_key.insufficient_scope', `API key requires scopes: ${requiredScopes.join(', ')}`)\n }\n const identity = await options.resolveIdentity(key)\n if (!identity) throw denied(401, 'api_key.invalid_owner', 'API key owner is unavailable')\n const claim = await options.claimRequest(key, crypto.randomUUID())\n if (claim.allowed !== true) {\n const headers = new Headers()\n if (Number.isFinite(claim.retryAfterSeconds) && claim.retryAfterSeconds! > 0) {\n headers.set('Retry-After', String(Math.ceil(claim.retryAfterSeconds!)))\n }\n throw denied(429, 'api_key.request_limit_exceeded', 'API key request limit exceeded', headers)\n }\n if (key.expiresAt <= Date.now()) {\n throw denied(401, 'api_key.expired', 'API key expired during authorization')\n }\n return identity\n }\n}\n\nfunction denied(status: number, code: string, error: string, headers?: Headers): Response {\n return Response.json({ error, code }, { status, headers })\n}\n"],"mappings":"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","names":[]}
|
package/dist/platform/sso.d.ts
CHANGED
|
@@ -42,15 +42,19 @@ export interface TangleSsoAuthClient {
|
|
|
42
42
|
exchange(code: string): Promise<TangleSsoExchangeResult>;
|
|
43
43
|
}
|
|
44
44
|
/** First-party login exchanges identity without minting a Platform API key. */
|
|
45
|
+
export interface TangleIdentitySsoExchangeResult {
|
|
46
|
+
kind: 'identity';
|
|
47
|
+
emailVerified: boolean;
|
|
48
|
+
user: TangleSsoExchangeResult['user'];
|
|
49
|
+
apiKey?: never;
|
|
50
|
+
keyId?: never;
|
|
51
|
+
}
|
|
45
52
|
export interface TangleIdentitySsoAuthClient {
|
|
46
53
|
authorizeUrl(options: {
|
|
47
54
|
state: string;
|
|
48
55
|
redirectUri?: string;
|
|
49
56
|
}): string;
|
|
50
|
-
exchange(code: string
|
|
51
|
-
emailVerified: boolean;
|
|
52
|
-
user: TangleSsoExchangeResult['user'];
|
|
53
|
-
}>;
|
|
57
|
+
exchange(code: string): Promise<TangleIdentitySsoExchangeResult>;
|
|
54
58
|
}
|
|
55
59
|
/** Token fields the callback persists from the runtime's OIDC exchange. */
|
|
56
60
|
export interface TangleOidcSsoTokens {
|
|
@@ -216,6 +220,13 @@ export interface TangleOidcSsoAccountStore extends Omit<TangleSsoAccountStore, '
|
|
|
216
220
|
}
|
|
217
221
|
/** Persist only the stable identity link for a first-party app. */
|
|
218
222
|
export interface TangleIdentitySsoAccountStore extends Omit<TangleSsoAccountStore, 'saveTangleLink'> {
|
|
223
|
+
/** `upsertUserByEmail` may create the local user, but must not publish a new
|
|
224
|
+
* platform link or update an existing user's verified-email state. The
|
|
225
|
+
* callback has not yet created a session or prepared its cookie. */
|
|
226
|
+
upsertUserByEmail: TangleSsoAccountStore['upsertUserByEmail'];
|
|
227
|
+
/** Commit the identity link and any verified-email update atomically. Do not
|
|
228
|
+
* reject after committing. If the store can partially write before throwing,
|
|
229
|
+
* `deleteSession` must remove only writes made for this session token. */
|
|
219
230
|
saveTangleLink(input: {
|
|
220
231
|
userId: string;
|
|
221
232
|
sessionToken: string;
|
|
@@ -223,6 +234,9 @@ export interface TangleIdentitySsoAccountStore extends Omit<TangleSsoAccountStor
|
|
|
223
234
|
email: string;
|
|
224
235
|
name: string | null;
|
|
225
236
|
}): Promise<void>;
|
|
237
|
+
/** Remove an unpublished session and any link or verification writes that a
|
|
238
|
+
* rejected `saveTangleLink` made for its exact token. Preserve preexisting
|
|
239
|
+
* links and other sessions. */
|
|
226
240
|
deleteSession(input: {
|
|
227
241
|
sessionToken: string;
|
|
228
242
|
}): Promise<void>;
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createChatTurnRoutes,
|
|
3
3
|
streamChatRouteAsSandboxEvents
|
|
4
|
-
} from "../chunk-
|
|
5
|
-
import "../chunk-
|
|
4
|
+
} from "../chunk-KJEW23GR.js";
|
|
5
|
+
import "../chunk-TZEEGDHS.js";
|
|
6
|
+
import "../chunk-3NCPXNBZ.js";
|
|
6
7
|
import "../chunk-ZSSCVT6H.js";
|
|
7
|
-
import "../chunk-
|
|
8
|
+
import "../chunk-RACQDXJ7.js";
|
|
8
9
|
import "../chunk-X47R2IVO.js";
|
|
9
|
-
import "../chunk-TXD5HXLE.js";
|
|
10
10
|
import "../chunk-ZVEEWGDK.js";
|
|
11
|
+
import "../chunk-TXD5HXLE.js";
|
|
11
12
|
import "../chunk-3NM4GCAY.js";
|
|
12
|
-
import "../chunk-
|
|
13
|
-
import "../chunk-M3K2HVQD.js";
|
|
13
|
+
import "../chunk-MX55JP7D.js";
|
|
14
14
|
import "../chunk-YJMCRXQQ.js";
|
|
15
|
+
import "../chunk-M3K2HVQD.js";
|
|
15
16
|
import "../chunk-HXTJXODG.js";
|
|
16
17
|
|
|
17
18
|
// src/public-consultation/index.ts
|