@tangle-network/agent-app 0.60.180 → 0.60.182
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/assistant/index.js +12 -12
- package/dist/chat-routes/index.js +23 -23
- package/dist/chat-store/index.js +4 -4
- package/dist/{chunk-6ZUVYAKB.js → chunk-6O2NQ7WD.js} +9 -9
- package/dist/{chunk-IIE3K2Y5.js → chunk-EOPFHXEA.js} +1 -1
- package/dist/{chunk-PYPWEXTD.js → chunk-J26IXWFK.js} +12 -12
- package/dist/chunk-LTWJYNEC.js +100 -0
- package/dist/chunk-LTWJYNEC.js.map +1 -0
- package/dist/{chunk-YR6TZ777.js → chunk-TNWDYAFX.js} +6 -6
- package/dist/{chunk-T5PPQPOD.js → chunk-TXUAGSFR.js} +1 -1
- package/dist/{chunk-T5PPQPOD.js.map → chunk-TXUAGSFR.js.map} +1 -1
- package/dist/{chunk-N3CYCSKK.js → chunk-ZBB5KQ6B.js} +1 -1
- package/dist/chunk-ZBB5KQ6B.js.map +1 -0
- package/dist/{chunk-XNKLQG26.js → chunk-ZGUXI5SI.js} +5 -5
- package/dist/design-canvas-react/index.js +4 -4
- package/dist/design-canvas-react/lazy.js +1 -1
- package/dist/model-resolution/allow-list.d.ts +64 -0
- package/dist/model-resolution/index.d.ts +1 -0
- package/dist/model-resolution/index.js +28 -0
- package/dist/model-resolution/index.js.map +1 -1
- package/dist/profile/index.js +2 -1
- package/dist/profile/index.js.map +1 -1
- package/dist/public-consultation/index.d.ts +1 -1
- package/dist/public-consultation/index.js +8 -8
- package/dist/skills/index.d.ts +1 -0
- package/dist/skills/index.js +26 -2
- package/dist/skills/marketplace-drizzle.d.ts +121 -0
- package/dist/skills/marketplace-drizzle.js +34 -0
- package/dist/skills/marketplace-drizzle.js.map +1 -0
- package/dist/skills/marketplace.d.ts +104 -0
- package/dist/skills-placement/index.js +2 -1
- package/dist/skills-placement/index.js.map +1 -1
- package/dist/spend/delegate-budget.d.ts +59 -0
- package/dist/spend/index.d.ts +1 -0
- package/dist/spend/index.js +43 -0
- package/dist/spend/index.js.map +1 -1
- package/dist/stream/index.js +2 -2
- package/dist/teams/drizzle/access.d.ts +4 -0
- package/dist/teams/drizzle.js.map +1 -1
- package/dist/teams/invitations-api.d.ts +6 -3
- package/dist/teams/invitations-api.js +72 -39
- package/dist/teams/invitations-api.js.map +1 -1
- package/dist/teams/members-api.d.ts +2 -2
- package/dist/teams/members-api.js +1 -1
- package/dist/toolbelt/index.d.ts +80 -0
- package/dist/toolbelt/index.js +178 -0
- package/dist/toolbelt/index.js.map +1 -0
- package/dist/web-react/index.js +25 -25
- package/package.json +11 -1
- package/dist/chunk-N3CYCSKK.js.map +0 -1
- /package/dist/{chunk-6ZUVYAKB.js.map → chunk-6O2NQ7WD.js.map} +0 -0
- /package/dist/{chunk-IIE3K2Y5.js.map → chunk-EOPFHXEA.js.map} +0 -0
- /package/dist/{chunk-PYPWEXTD.js.map → chunk-J26IXWFK.js.map} +0 -0
- /package/dist/{chunk-YR6TZ777.js.map → chunk-TNWDYAFX.js.map} +0 -0
- /package/dist/{chunk-XNKLQG26.js.map → chunk-ZGUXI5SI.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/teams/members-api.ts"],"sourcesContent":["/**\n * Framework-neutral members API for the teams module: the invite / list /\n * update-role / remove / accept-invite logic, lifted out of any one app's route\n * file. Each app mounts these in its own route with its own auth — the handlers\n * take an already-authenticated `actor`, the parsed inputs, the `db` + `tables`\n * from `createTeamTables`, the product's user + workspace tables, and the\n * workspace-access API from `createWorkspaceAccess`. They return web-standard\n * `Response`s (available in Workers, Node 18+, Deno, browsers), so\n * \"framework-neutral\" is literal: no Remix/React-Router/Express import anywhere.\n *\n * Two OPTIONAL seams an app wires only if it needs them:\n * - `enforceSeat` — billing/seat-limit gate, called at invite time only when\n * the invite would consume a NEW billable seat. An app without seat billing\n * passes nothing and seats are never checked.\n * - `memberSyncSeam` — fire-and-forget propagation of membership changes to a\n * sandbox/external system (add on accept, role on update, remove on\n * delete). An app without sandbox sync passes nothing. Fail-soft by\n * contract: a thrown sync is caught and never blocks the DB mutation.\n *\n * Imports `drizzle-orm`, so this is a subpath, never re-exported from root.\n */\n\nimport { and, eq, isNull } from 'drizzle-orm'\nimport { INVITATION_EMAIL_HINT, isInvitationEmail } from './invitations'\nimport {\n type AssignableWorkspaceRole,\n type WorkspaceRole,\n canManageWorkspaceMemberRole,\n hasWorkspaceRole,\n isAssignableWorkspaceRole,\n organizationRoleGrantsWorkspaceOwner,\n} from './roles'\nimport { generateInviteToken } from './invite'\nimport type { TeamDatabase, WorkspaceAccessReader } from './drizzle/access'\nimport type { TeamParentTable, TeamTables } from './drizzle/schema'\n\n/** The authenticated caller — apps resolve this from their own session layer. */\nexport interface MembersApiActor {\n id: string\n email?: string | null\n}\n\n/**\n * The product's user table, narrowed to the columns the member queries read.\n * Adopters pass their real drizzle user table.\n */\nexport interface UserLookupTable {\n id: any\n name: any\n email: any\n}\n\n/**\n * The product's workspace table, narrowed to the columns the member queries\n * read. The handlers join it to resolve a workspace's organization (the same\n * table passed to createTeamTables as workspaceTable).\n */\nexport interface WorkspaceLookupTable {\n id: any\n organizationId: any\n}\n\n/**\n * Optional billing seat gate. The seam resolves the count input itself (it\n * owns the plan/seat model) and throws when over the limit; the handler turns a\n * thrown `SeatLimitError` into a 402. Called only when an invite would consume\n * a NEW seat (invitee is not already an org member and has no pending org\n * invite) — the same guard gtm uses, so the seam never fires on a no-op invite.\n */\nexport interface EnforceSeatSeam {\n (input: { actorId: string; organizationId: string }): Promise<void> | void\n}\n\n/** Thrown by an `enforceSeat` seam to deny an invite; serialized to a 402. */\nexport class SeatLimitError extends Error {\n readonly status: number\n readonly capability?: string\n readonly requiredPlan?: string\n constructor(message: string, opts: { status?: number; capability?: string; requiredPlan?: string } = {}) {\n super(message)\n this.name = 'SeatLimitError'\n this.status = opts.status ?? 402\n this.capability = opts.capability\n this.requiredPlan = opts.requiredPlan\n }\n}\n\n/**\n * Optional membership-change propagation to an external system (e.g. sandbox).\n * Every method is fire-and-forget and fail-soft: the handler awaits nothing and\n * swallows rejections, so an unavailable downstream never blocks the mutation.\n * Each fires only for members with a real `userId` (never email-only invites).\n */\nexport interface MemberSyncSeam {\n add?(input: { workspaceId: string; userId: string; role: WorkspaceRole }): Promise<void> | void\n role?(input: { workspaceId: string; userId: string; role: WorkspaceRole }): Promise<void> | void\n remove?(input: { workspaceId: string; userId: string }): Promise<void> | void\n}\n\n/** Define configuration options for managing team members and workspace access APIs */\nexport interface MembersApiOptions {\n db: TeamDatabase\n tables: TeamTables\n /** The product's user table (FK target passed to createTeamTables). */\n userTable: TeamParentTable & UserLookupTable\n /** The product's workspace table (FK target passed to createTeamTables). */\n workspaceTable: TeamParentTable & WorkspaceLookupTable\n /** Workspace-access API from createWorkspaceAccess — RBAC stays one source. */\n access: WorkspaceAccessReader\n enforceSeat?: EnforceSeatSeam\n memberSyncSeam?: MemberSyncSeam\n}\n\n/** Define the structure of a workspace member entry with identification, role, and status details */\nexport interface MemberListEntry {\n id: string\n userId: string | null\n organizationMemberId: string | null\n role: WorkspaceRole\n name: string | null\n email: string | null\n invitedAt: Date | number | null\n acceptedAt: Date | number | null\n inherited: boolean\n}\n\n/**\n * Build the members API bound to one product's db/tables/access. Returns five\n * handlers; an app maps its route methods onto them (GET→list, POST→invite,\n * PATCH→updateRole, DELETE→remove; accept on its own route).\n */\nexport function createMembersApi(opts: MembersApiOptions) {\n const { db, tables, userTable, workspaceTable, access, enforceSeat, memberSyncSeam } = opts\n const { organizations, organizationMembers, workspaceMembers } = tables\n const users = userTable\n const workspaces = workspaceTable\n\n function fireSync(op: () => Promise<void> | void) {\n try {\n Promise.resolve(op()).catch(() => {})\n } catch {\n // seam threw synchronously — swallow; sync is best-effort by contract.\n }\n }\n\n async function listMembers(input: { workspaceId: string; actor: MembersApiActor }): Promise<Response> {\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id)\n if (!accessRow) return Response.json({ error: 'Workspace not found' }, { status: 404 })\n\n const organizationId = (accessRow.workspace as { organizationId: string }).organizationId\n\n const [projectMembers, orgAdmins] = await Promise.all([\n db\n .select({\n id: workspaceMembers.id,\n organizationMemberId: workspaceMembers.organizationMemberId,\n userId: workspaceMembers.userId,\n role: workspaceMembers.role,\n invitedAt: workspaceMembers.invitedAt,\n acceptedAt: workspaceMembers.acceptedAt,\n inviteEmail: workspaceMembers.inviteEmail,\n userName: users.name,\n userEmail: users.email,\n })\n .from(workspaceMembers)\n .leftJoin(users, eq(users.id, workspaceMembers.userId))\n .where(eq(workspaceMembers.workspaceId, input.workspaceId)),\n db\n .select({\n id: organizationMembers.id,\n userId: organizationMembers.userId,\n orgRole: organizationMembers.role,\n userName: users.name,\n userEmail: users.email,\n createdAt: organizationMembers.createdAt,\n })\n .from(organizationMembers)\n .innerJoin(users, eq(users.id, organizationMembers.userId))\n .where(eq(organizationMembers.organizationId, organizationId)),\n ])\n\n const explicitOrgMemberIds = new Set(\n projectMembers.map((m: any) => m.organizationMemberId).filter(Boolean),\n )\n const members: MemberListEntry[] = [\n ...orgAdmins\n .filter((m: any) => organizationRoleGrantsWorkspaceOwner(m.orgRole) && !explicitOrgMemberIds.has(m.id))\n .map((m: any) => ({\n id: `org:${m.id}`,\n userId: m.userId,\n organizationMemberId: m.id,\n role: 'owner' as WorkspaceRole,\n name: m.userName,\n email: m.userEmail,\n invitedAt: m.createdAt,\n acceptedAt: m.createdAt,\n inherited: true,\n })),\n ...projectMembers.map((m: any) => ({\n id: m.id,\n userId: m.userId,\n organizationMemberId: m.organizationMemberId,\n role: m.role as WorkspaceRole,\n name: m.userName,\n email: m.userEmail ?? m.inviteEmail,\n invitedAt: m.invitedAt,\n acceptedAt: m.acceptedAt,\n inherited: false,\n })),\n ]\n\n return Response.json({ members, currentRole: accessRow.role, organizationId })\n }\n\n async function inviteMember(input: {\n workspaceId: string\n actor: MembersApiActor\n email?: string\n role?: string\n }): Promise<Response> {\n if (!input.email) return Response.json({ error: 'Missing email' }, { status: 400 })\n if (!isInvitationEmail(input.email)) return Response.json({ error: INVITATION_EMAIL_HINT }, { status: 400 })\n const requestedRole = input.role ?? 'editor'\n if (!isAssignableWorkspaceRole(requestedRole)) {\n return Response.json({ error: 'Invalid role. Must be viewer, editor, or admin.' }, { status: 400 })\n }\n const assignRole: AssignableWorkspaceRole = requestedRole\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id, 'admin')\n if (!accessRow) return Response.json({ error: 'Workspace not found' }, { status: 404 })\n if (!hasWorkspaceRole(accessRow.role, assignRole)) {\n return Response.json({ error: 'Cannot assign a role higher than your own' }, { status: 403 })\n }\n\n const organizationId = (accessRow.workspace as { organizationId: string }).organizationId\n const normalizedEmail = input.email.toLowerCase().trim()\n const invitee = await getUserByEmail(normalizedEmail)\n const existingOrgMember = invitee ? await getOrganizationMember(organizationId, invitee.id) : null\n\n if (organizationRoleGrantsWorkspaceOwner(existingOrgMember?.role)) {\n return Response.json({ error: 'Organization admins already have project access' }, { status: 409 })\n }\n\n const existingInvite = await findExistingProjectInvite(input.workspaceId, normalizedEmail, invitee?.id)\n if (existingInvite) {\n return Response.json({ error: 'This collaborator is already invited to this project' }, { status: 409 })\n }\n\n if (enforceSeat && !existingOrgMember && !(await hasPendingOrgInvite(organizationId, normalizedEmail))) {\n try {\n await enforceSeat({ actorId: input.actor.id, organizationId })\n } catch (err) {\n if (err instanceof SeatLimitError) {\n return Response.json(\n { error: err.message, capability: err.capability, requiredPlan: err.requiredPlan },\n { status: err.status },\n )\n }\n throw err\n }\n }\n\n const token = generateInviteToken()\n const [member] = await db\n .insert(workspaceMembers)\n .values({\n workspaceId: input.workspaceId,\n organizationMemberId: existingOrgMember?.id ?? null,\n userId: invitee?.id ?? null,\n role: assignRole,\n invitedBy: input.actor.id,\n inviteEmail: normalizedEmail,\n inviteToken: token,\n })\n .returning()\n\n return Response.json({ member, inviteToken: token })\n }\n\n async function updateMemberRole(input: {\n workspaceId: string\n actor: MembersApiActor\n memberId?: string\n role?: string\n }): Promise<Response> {\n if (!input.memberId || !input.role) {\n return Response.json({ error: 'Missing memberId or role' }, { status: 400 })\n }\n if (input.memberId.startsWith('org:')) {\n return Response.json({ error: 'Organization owner/admin project access is managed at the organization level' }, { status: 403 })\n }\n if (!isAssignableWorkspaceRole(input.role)) {\n return Response.json({ error: 'Invalid role' }, { status: 400 })\n }\n\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id, 'admin')\n if (!accessRow) return Response.json({ error: 'Workspace not found' }, { status: 404 })\n if (!hasWorkspaceRole(accessRow.role, input.role)) {\n return Response.json({ error: 'Cannot assign a role higher than your own' }, { status: 403 })\n }\n\n const [target] = await db\n .select({ id: workspaceMembers.id, role: workspaceMembers.role, userId: workspaceMembers.userId })\n .from(workspaceMembers)\n .where(and(eq(workspaceMembers.id, input.memberId), eq(workspaceMembers.workspaceId, input.workspaceId)))\n .limit(1)\n\n if (!target) return Response.json({ error: 'Member not found' }, { status: 404 })\n if (target.userId === input.actor.id) return Response.json({ error: 'Cannot change your own role' }, { status: 403 })\n if (!canManageWorkspaceMemberRole(accessRow.role, target.role as WorkspaceRole)) {\n return Response.json({ error: 'Cannot modify a member with equal or higher role' }, { status: 403 })\n }\n\n await db.update(workspaceMembers).set({ role: input.role }).where(eq(workspaceMembers.id, input.memberId))\n if (target.userId && memberSyncSeam?.role) {\n const userId = target.userId\n const nextRole = input.role as WorkspaceRole\n fireSync(() => memberSyncSeam.role!({ workspaceId: input.workspaceId, userId, role: nextRole }))\n }\n return Response.json({ success: true })\n }\n\n async function removeMember(input: {\n workspaceId: string\n actor: MembersApiActor\n memberId?: string\n }): Promise<Response> {\n if (!input.memberId) return Response.json({ error: 'Missing memberId' }, { status: 400 })\n if (input.memberId.startsWith('org:')) {\n return Response.json({ error: 'Organization owner/admin project access is managed at the organization level' }, { status: 403 })\n }\n\n const accessRow = await access.getWorkspaceAccess(input.workspaceId, input.actor.id, 'admin')\n if (!accessRow) return Response.json({ error: 'Workspace not found' }, { status: 404 })\n\n const [target] = await db\n .select({ id: workspaceMembers.id, role: workspaceMembers.role, userId: workspaceMembers.userId })\n .from(workspaceMembers)\n .where(and(eq(workspaceMembers.id, input.memberId), eq(workspaceMembers.workspaceId, input.workspaceId)))\n .limit(1)\n\n if (!target) return Response.json({ error: 'Member not found' }, { status: 404 })\n if (target.userId === input.actor.id) return Response.json({ error: 'Cannot remove yourself from this project' }, { status: 403 })\n if (!canManageWorkspaceMemberRole(accessRow.role, target.role as WorkspaceRole)) {\n return Response.json({ error: 'Cannot remove a member with equal or higher role' }, { status: 403 })\n }\n\n await db.delete(workspaceMembers).where(eq(workspaceMembers.id, input.memberId))\n if (target.userId && memberSyncSeam?.remove) {\n const userId = target.userId\n fireSync(() => memberSyncSeam.remove!({ workspaceId: input.workspaceId, userId }))\n }\n return Response.json({ success: true })\n }\n\n async function acceptInvite(input: { token?: string; actor: MembersApiActor }): Promise<Response> {\n if (!input.token || typeof input.token !== 'string') {\n return Response.json({ error: 'Missing invite token' }, { status: 400 })\n }\n\n const [invite] = await db\n .select()\n .from(workspaceMembers)\n .where(eq(workspaceMembers.inviteToken, input.token))\n .limit(1)\n\n if (!invite) return Response.json({ error: 'Invalid or expired invite' }, { status: 404 })\n if (invite.acceptedAt) return Response.json({ error: 'Invite already accepted' }, { status: 409 })\n\n if (invite.inviteEmail && invite.inviteEmail.toLowerCase() !== input.actor.email?.toLowerCase()) {\n return Response.json(\n { error: `This invite was sent to ${invite.inviteEmail}. Please sign in with that email.` },\n { status: 403 },\n )\n }\n\n const [workspace] = await db\n .select({ id: workspaces.id, organizationId: workspaces.organizationId })\n .from(workspaces)\n .where(eq(workspaces.id, invite.workspaceId))\n .limit(1)\n\n if (!workspace) return Response.json({ error: 'Project not found' }, { status: 404 })\n const organizationId = (workspace as { organizationId: string }).organizationId\n\n // Atomic accept: only succeeds if the token still exists and is unaccepted,\n // so two concurrent accepts can't both win.\n const updated = await db\n .update(workspaceMembers)\n .set({\n userId: input.actor.id,\n acceptedAt: new Date(),\n inviteToken: null,\n })\n .where(and(\n eq(workspaceMembers.inviteToken, input.token),\n isNull(workspaceMembers.acceptedAt),\n ))\n .returning({ id: workspaceMembers.id })\n\n if (updated.length === 0) return Response.json({ error: 'Invite already accepted' }, { status: 409 })\n\n const [orgMember] = await db\n .insert(organizationMembers)\n .values({\n organizationId,\n userId: input.actor.id,\n role: 'member',\n })\n .onConflictDoUpdate({\n target: [organizationMembers.organizationId, organizationMembers.userId],\n set: { updatedAt: new Date() },\n })\n .returning()\n\n await db\n .update(workspaceMembers)\n .set({ organizationMemberId: invite.organizationMemberId ?? orgMember!.id })\n .where(eq(workspaceMembers.id, updated[0]!.id))\n\n if (memberSyncSeam?.add) {\n const role = invite.role as WorkspaceRole\n fireSync(() => memberSyncSeam.add!({ workspaceId: invite.workspaceId, userId: input.actor.id, role }))\n }\n\n return Response.json({\n success: true,\n workspaceId: invite.workspaceId,\n role: invite.role,\n })\n }\n\n // ── internal query helpers ──\n\n async function getUserByEmail(email: string) {\n const [user] = await db\n .select({ id: users.id, email: users.email })\n .from(users)\n .where(eq(users.email, email))\n .limit(1)\n return user ?? null\n }\n\n async function getOrganizationMember(organizationId: string, userId: string) {\n const [member] = await db\n .select()\n .from(organizationMembers)\n .where(and(eq(organizationMembers.organizationId, organizationId), eq(organizationMembers.userId, userId)))\n .limit(1)\n return member ?? null\n }\n\n async function findExistingProjectInvite(workspaceId: string, email: string, userId?: string) {\n const byEmail = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .where(and(eq(workspaceMembers.workspaceId, workspaceId), eq(workspaceMembers.inviteEmail, email)))\n .limit(1)\n if (byEmail[0]) return byEmail[0]\n if (!userId) return null\n const byUser = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .where(and(eq(workspaceMembers.workspaceId, workspaceId), eq(workspaceMembers.userId, userId)))\n .limit(1)\n return byUser[0] ?? null\n }\n\n async function hasPendingOrgInvite(organizationId: string, email: string): Promise<boolean> {\n const [row] = await db\n .select({ id: workspaceMembers.id })\n .from(workspaceMembers)\n .innerJoin(workspaces, eq(workspaces.id, workspaceMembers.workspaceId))\n .where(and(\n eq(workspaces.organizationId, organizationId),\n eq(workspaceMembers.inviteEmail, email),\n isNull(workspaceMembers.acceptedAt),\n ))\n .limit(1)\n return Boolean(row)\n }\n\n return { listMembers, inviteMember, updateMemberRole, removeMember, acceptInvite }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
+
import {
|
|
2
|
+
canTransitionInteractionStatus,
|
|
3
|
+
persistedPartToInteraction
|
|
4
|
+
} from "./chunk-6VQCFJWP.js";
|
|
1
5
|
import {
|
|
2
6
|
canTransitionPlanStatus,
|
|
3
7
|
persistedPartToPlan,
|
|
4
8
|
planPartKey,
|
|
5
9
|
planToPersistedPart
|
|
6
10
|
} from "./chunk-LQYIBL3G.js";
|
|
7
|
-
import {
|
|
8
|
-
canTransitionInteractionStatus,
|
|
9
|
-
persistedPartToInteraction
|
|
10
|
-
} from "./chunk-6VQCFJWP.js";
|
|
11
11
|
|
|
12
12
|
// src/stream/stream-normalizer.ts
|
|
13
13
|
function asRecord(value) {
|
|
@@ -406,4 +406,4 @@ export {
|
|
|
406
406
|
terminalizeDanglingAssistantToolUpdates,
|
|
407
407
|
encodeEvent
|
|
408
408
|
};
|
|
409
|
-
//# sourceMappingURL=chunk-
|
|
409
|
+
//# sourceMappingURL=chunk-ZGUXI5SI.js.map
|
|
@@ -84,15 +84,15 @@ import {
|
|
|
84
84
|
setPagePropsCommand,
|
|
85
85
|
ungroupElementCommand
|
|
86
86
|
} from "../chunk-6NYVAURR.js";
|
|
87
|
-
import {
|
|
88
|
-
DesignCanvasChromeLazy,
|
|
89
|
-
DesignCanvasLazy
|
|
90
|
-
} from "../chunk-IIE3K2Y5.js";
|
|
91
87
|
import {
|
|
92
88
|
assertSceneMediaSrc,
|
|
93
89
|
bleedAwareExportBounds,
|
|
94
90
|
scaleForPreset
|
|
95
91
|
} from "../chunk-OHWQUKBP.js";
|
|
92
|
+
import {
|
|
93
|
+
DesignCanvasChromeLazy,
|
|
94
|
+
DesignCanvasLazy
|
|
95
|
+
} from "../chunk-EOPFHXEA.js";
|
|
96
96
|
import "../chunk-UTAIRVOK.js";
|
|
97
97
|
import "../chunk-RN4WPTBH.js";
|
|
98
98
|
import "../chunk-3NCPXNBZ.js";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live model routing by allow-list — the positive form of `model_not_allowed`.
|
|
3
|
+
*
|
|
4
|
+
* A scoped key (QA keys, delegated child keys, provisioned product keys)
|
|
5
|
+
* carries an allowed-model set. The router enforces it as a wall:
|
|
6
|
+
*
|
|
7
|
+
* 400 model_not_allowed — `Model "gpt-6-luna" is not in this key's
|
|
8
|
+
* allow-list. Allowed: glm-5.3, gpt-5.5.`
|
|
9
|
+
*
|
|
10
|
+
* The refusal already NAMES the allowed set — so a product can treat the
|
|
11
|
+
* allow-list as routing information instead of a dead end: parse the set,
|
|
12
|
+
* pick the first preferred model it permits, and retry. Combined with the
|
|
13
|
+
* router's key-info endpoint (which exposes `allowed_models` before the
|
|
14
|
+
* first refusal), a product can route right the first time.
|
|
15
|
+
*
|
|
16
|
+
* Fail-closed by design: when no preferred model is allowed, the resolver
|
|
17
|
+
* returns `status: 'refused'` with the allowed set — the caller surfaces a
|
|
18
|
+
* named refusal (via {@link formatModelNotAllowedRefusal}) rather than
|
|
19
|
+
* silently downgrading to an arbitrary model. A different model is a
|
|
20
|
+
* different decision-maker; the product's fallback ORDER is a product
|
|
21
|
+
* decision, and this module only executes it.
|
|
22
|
+
*/
|
|
23
|
+
/** Parse the allowed-model set out of a router `model_not_allowed` refusal.
|
|
24
|
+
* Both refusal shapes name the set after `Allowed:` —
|
|
25
|
+
* `Model "x" is not in this key's allow-list. Allowed: a, b, c.` and
|
|
26
|
+
* `Fallback model(s) "x" are not in this key's allow-list. Allowed: a, b.`
|
|
27
|
+
* Returns null when the message carries no set (an unexpected shape — do
|
|
28
|
+
* not guess). */
|
|
29
|
+
export declare function parseModelNotAllowed(message: string): string[] | null;
|
|
30
|
+
export interface ResolveModelWithinAllowListInput {
|
|
31
|
+
/** The model the product wants to run. */
|
|
32
|
+
readonly requested: string;
|
|
33
|
+
/** The key's allowed-model set (empty/absent = the key is unscoped; the
|
|
34
|
+
* request stands). */
|
|
35
|
+
readonly allowedModels?: readonly string[] | null;
|
|
36
|
+
/** The product's reroute order when `requested` is blocked — tried in
|
|
37
|
+
* order, first allowed wins. */
|
|
38
|
+
readonly fallbacks?: readonly string[];
|
|
39
|
+
}
|
|
40
|
+
export type ModelWithinAllowListResolution = {
|
|
41
|
+
readonly status: 'allowed';
|
|
42
|
+
readonly model: string;
|
|
43
|
+
readonly rerouted: false;
|
|
44
|
+
} | {
|
|
45
|
+
readonly status: 'rerouted';
|
|
46
|
+
readonly model: string;
|
|
47
|
+
readonly rerouted: true;
|
|
48
|
+
readonly blockedModel: string;
|
|
49
|
+
} | {
|
|
50
|
+
readonly status: 'refused';
|
|
51
|
+
readonly model: null;
|
|
52
|
+
readonly allowedModels: readonly string[];
|
|
53
|
+
};
|
|
54
|
+
/** Resolve a model request against the key's allow-list:
|
|
55
|
+
* unscoped → allowed as-is; requested permitted → allowed; a fallback
|
|
56
|
+
* permitted → rerouted (named); nothing permitted → refused with the set. */
|
|
57
|
+
export declare function resolveModelWithinAllowList(input: ResolveModelWithinAllowListInput): ModelWithinAllowListResolution;
|
|
58
|
+
/** Format the loud, correctable refusal: the blocked model, the allowed set,
|
|
59
|
+
* and the fix. Mirrors the router's own refusal text so both layers name
|
|
60
|
+
* the same fact. */
|
|
61
|
+
export declare function formatModelNotAllowedRefusal(input: {
|
|
62
|
+
requested: string;
|
|
63
|
+
allowedModels: readonly string[];
|
|
64
|
+
}): string;
|
|
@@ -84,4 +84,5 @@ export declare function cleanModelId(value: unknown): string | undefined;
|
|
|
84
84
|
export declare function isWellFormedModelId(modelId: string): boolean;
|
|
85
85
|
/** Resolve unique catalog IDs associated with a given model including its canonical form if applicable */
|
|
86
86
|
export declare function catalogIdsForModel(model: ModelInfo): string[];
|
|
87
|
+
export { parseModelNotAllowed, resolveModelWithinAllowList, formatModelNotAllowedRefusal, type ResolveModelWithinAllowListInput, type ModelWithinAllowListResolution, } from './allow-list';
|
|
87
88
|
export { isUpstreamUnavailable, readHttpStatusHint, runWithModelFailover, buildModelChain, ModelFailoverExhaustedError, UPSTREAM_UNAVAILABLE_CODES, UPSTREAM_UNAVAILABLE_STATUSES, type ModelFailoverAttempt, type ModelFailoverResult, type RunWithModelFailoverInput, } from './failover';
|
|
@@ -8,6 +8,31 @@ import {
|
|
|
8
8
|
runWithModelFailover
|
|
9
9
|
} from "../chunk-63EKFZOE.js";
|
|
10
10
|
|
|
11
|
+
// src/model-resolution/allow-list.ts
|
|
12
|
+
function parseModelNotAllowed(message) {
|
|
13
|
+
const match = /Allowed:\s*([^\n]+?)(?:\.\s*)?$/.exec(message);
|
|
14
|
+
if (!match?.[1]) return null;
|
|
15
|
+
const allowed = match[1].split(",").map((id) => id.trim().replace(/^["']|["']$/g, "")).filter(Boolean);
|
|
16
|
+
return allowed.length > 0 ? allowed : null;
|
|
17
|
+
}
|
|
18
|
+
function resolveModelWithinAllowList(input) {
|
|
19
|
+
const allowed = input.allowedModels;
|
|
20
|
+
if (!allowed || allowed.length === 0) {
|
|
21
|
+
return { status: "allowed", model: input.requested, rerouted: false };
|
|
22
|
+
}
|
|
23
|
+
if (allowed.includes(input.requested)) {
|
|
24
|
+
return { status: "allowed", model: input.requested, rerouted: false };
|
|
25
|
+
}
|
|
26
|
+
const fallback = (input.fallbacks ?? []).find((id) => allowed.includes(id));
|
|
27
|
+
if (fallback !== void 0) {
|
|
28
|
+
return { status: "rerouted", model: fallback, rerouted: true, blockedModel: input.requested };
|
|
29
|
+
}
|
|
30
|
+
return { status: "refused", model: null, allowedModels: allowed };
|
|
31
|
+
}
|
|
32
|
+
function formatModelNotAllowedRefusal(input) {
|
|
33
|
+
return `Model "${input.requested}" is not in this key's allow-list. Allowed: ${input.allowedModels.join(", ")}. Route to an allowed model or widen the key\u2019s allow-list.`;
|
|
34
|
+
}
|
|
35
|
+
|
|
11
36
|
// src/model-resolution/index.ts
|
|
12
37
|
function canonicalModelId(model) {
|
|
13
38
|
if (model.id.includes("/")) return model.id;
|
|
@@ -83,10 +108,13 @@ export {
|
|
|
83
108
|
buildModelChain,
|
|
84
109
|
catalogIdsForModel,
|
|
85
110
|
cleanModelId,
|
|
111
|
+
formatModelNotAllowedRefusal,
|
|
86
112
|
isUpstreamUnavailable,
|
|
87
113
|
isWellFormedModelId,
|
|
114
|
+
parseModelNotAllowed,
|
|
88
115
|
readHttpStatusHint,
|
|
89
116
|
resolveChatModel,
|
|
117
|
+
resolveModelWithinAllowList,
|
|
90
118
|
runWithModelFailover,
|
|
91
119
|
validateChatModelId
|
|
92
120
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/model-resolution/index.ts"],"sourcesContent":["/**\n * Canonical chat-model resolution — identical across every agent app.\n *\n * The ONLY per-app inputs are DATA, never logic: the default model, the\n * allowlist, the env value the deployment set, and the catalog-fetch loader.\n * The logic is one precedence ladder + one fail-closed validator that every\n * product uses the same way — there is no per-product variant, no env-var name\n * baked in, and no backend dimension (router-vs-sandbox is the harness/dispatch\n * concern, not model resolution; a sandbox's provider default lives in the\n * sandbox subpath).\n *\n * - resolveChatModel: request > workspace > env > default. The product reads its\n * own deploy env var and passes the VALUE as `envModel`; the shell knows no\n * env-var names. Source is canonical: 'request' | 'workspace' | 'env' | 'default'.\n * - validateChatModelId: fail-closed. Admit an id that is in the allowlist, or\n * equals the operator-set env model, or is served by the live router catalog\n * (exact, or a bare id resolved to its canonical id when the suffix is unique).\n */\n\n/** The router /v1/models entry shape this module reads. Minimal on purpose. */\nexport interface ModelInfo {\n id: string\n name?: string\n _provider?: string\n provider?: string\n}\n\n/** Canonical (provider-prefixed) id for a catalog entry: pass through an id that\n * already carries a provider, else prefix the entry's provider when present. */\nfunction canonicalModelId(model: ModelInfo): string {\n if (model.id.includes('/')) return model.id\n const provider = model._provider ?? model.provider\n return provider ? `${provider}/${model.id}` : model.id\n}\n\n/** Define possible origins for the chat model configuration values */\nexport type ChatModelSource = 'request' | 'workspace' | 'env' | 'default'\n\n/** Resolve a chat model with its identifier and source information */\nexport interface ResolvedChatModel {\n model: string\n source: ChatModelSource\n}\n\n/** Represent successful chat model validation with a true status and a validated string value */\nexport interface ChatModelValidationSuccess {\n succeeded: true\n value: string\n}\n\n/** Describe a failed chat model validation result with an error message */\nexport interface ChatModelValidationFailure {\n succeeded: false\n error: string\n}\n\n/** Resolve the outcome of validating a chat model as either success or failure */\nexport type ChatModelValidationResult = ChatModelValidationSuccess | ChatModelValidationFailure\n\n/** The catalog-fetch boundary: maps a router base URL to the raw model list. */\nexport type LoadModels = (routerBaseUrl: string) => Promise<ModelInfo[]>\n\n/** Resolve the effective chat model input by prioritizing request, workspace, environment, and default models */\nexport interface ResolveChatModelInput {\n /** Per-request override (highest precedence). */\n requestModel?: string\n /** Persisted workspace-pinned model. */\n workspaceModel?: string\n /** The value the deployment's model env var holds (the product reads its own\n * var name and passes the value — the shell stays env-var-name agnostic). */\n envModel?: string\n /** Final fallback (the product's default, typically profile.model.default). */\n defaultModel: string\n}\n\n/** Resolve the chat-turn model by the one canonical precedence. Blank values are\n * treated as absent. */\nexport function resolveChatModel(input: ResolveChatModelInput): ResolvedChatModel {\n const request = cleanModelId(input.requestModel)\n if (request) return { model: request, source: 'request' }\n const workspace = cleanModelId(input.workspaceModel)\n if (workspace) return { model: workspace, source: 'workspace' }\n const env = cleanModelId(input.envModel)\n if (env) return { model: env, source: 'env' }\n return { model: input.defaultModel, source: 'default' }\n}\n\n/** Define input parameters for validating chat model IDs with optional allowlist and catalog access details */\nexport interface ValidateChatModelIdInput {\n /** Ids accepted without a catalog round-trip (defaults + operator-trusted). */\n allowlist?: Iterable<string>\n /** The operator-set env model value — always admitted (operator-trusted). */\n envModel?: string\n /** Catalog loader; required to reach the catalog path. */\n loadModels?: LoadModels\n /** Catalog endpoint base; required to reach the catalog path. */\n routerBaseUrl?: string\n}\n\n/**\n * Fail-closed model-id validation. Accepts an id only when it is well-formed AND\n * (in the allowlist, or equals the operator-set env model, or served by the live\n * catalog). A bare id (no provider prefix) resolves to its canonical id only when\n * the suffix is unique across the catalog — an ambiguous suffix is rejected\n * rather than silently assigned a provider.\n */\nexport async function validateChatModelId(\n modelId: unknown,\n input: ValidateChatModelIdInput,\n): Promise<ChatModelValidationResult> {\n const cleaned = cleanModelId(modelId)\n if (!cleaned) return { succeeded: false, error: 'Model id must be a non-empty string.' }\n if (!isWellFormedModelId(cleaned)) return { succeeded: false, error: `Model id is malformed: ${cleaned}` }\n\n const allowed = new Set(input.allowlist ?? [])\n if (allowed.has(cleaned)) return { succeeded: true, value: cleaned }\n\n // The operator-set env model is trusted without a catalog round-trip.\n if (cleanModelId(input.envModel) === cleaned) return { succeeded: true, value: cleaned }\n\n if (!input.loadModels || typeof input.routerBaseUrl !== 'string' || input.routerBaseUrl.length === 0) {\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n }\n\n let catalog: ModelInfo[]\n try {\n catalog = await input.loadModels(input.routerBaseUrl)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return { succeeded: false, error: `Could not validate model catalog: ${message}` }\n }\n\n const ids = new Set(catalog.flatMap(catalogIdsForModel))\n if (ids.has(cleaned)) return { succeeded: true, value: cleaned }\n\n if (!cleaned.includes('/')) {\n const canonicalBySuffix = new Map<string, string[]>()\n for (const model of catalog) {\n if (typeof model.id !== 'string' || !model.id.trim()) continue\n const canonical = canonicalModelId(model)\n if (!canonical.includes('/')) continue\n const suffix = canonical.split('/').slice(1).join('/')\n const entries = canonicalBySuffix.get(suffix)\n if (entries) entries.push(canonical)\n else canonicalBySuffix.set(suffix, [canonical])\n }\n const matches = canonicalBySuffix.get(cleaned)\n if (matches && matches.length === 1) return { succeeded: true, value: matches[0]! }\n }\n\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n}\n\n/** Resolve and return a trimmed string model ID or undefined for invalid or empty input */\nexport function cleanModelId(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/** Validate if a model ID string conforms to length and character format requirements */\nexport function isWellFormedModelId(modelId: string): boolean {\n if (modelId.length > 200) return false\n return /^[A-Za-z0-9._/@:-]+$/.test(modelId)\n}\n\n/** Resolve unique catalog IDs associated with a given model including its canonical form if applicable */\nexport function catalogIdsForModel(model: ModelInfo): string[] {\n const ids = new Set<string>()\n if (typeof model.id === 'string' && model.id.trim()) ids.add(model.id.trim())\n if (typeof model.id === 'string' && model.id.trim() && !model.id.includes('/')) {\n const canonical = canonicalModelId(model)\n if (canonical.includes('/')) ids.add(canonical)\n }\n return [...ids]\n}\n\n// Reactive failover for the case validation cannot catch: a model that IS in the\n// catalog but whose upstream is down. Catalog membership is not liveness.\nexport {\n isUpstreamUnavailable,\n readHttpStatusHint,\n runWithModelFailover,\n buildModelChain,\n ModelFailoverExhaustedError,\n UPSTREAM_UNAVAILABLE_CODES,\n UPSTREAM_UNAVAILABLE_STATUSES,\n type ModelFailoverAttempt,\n type ModelFailoverResult,\n type RunWithModelFailoverInput,\n} from './failover'\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/model-resolution/allow-list.ts","../../src/model-resolution/index.ts"],"sourcesContent":["/**\n * Live model routing by allow-list — the positive form of `model_not_allowed`.\n *\n * A scoped key (QA keys, delegated child keys, provisioned product keys)\n * carries an allowed-model set. The router enforces it as a wall:\n *\n * 400 model_not_allowed — `Model \"gpt-6-luna\" is not in this key's\n * allow-list. Allowed: glm-5.3, gpt-5.5.`\n *\n * The refusal already NAMES the allowed set — so a product can treat the\n * allow-list as routing information instead of a dead end: parse the set,\n * pick the first preferred model it permits, and retry. Combined with the\n * router's key-info endpoint (which exposes `allowed_models` before the\n * first refusal), a product can route right the first time.\n *\n * Fail-closed by design: when no preferred model is allowed, the resolver\n * returns `status: 'refused'` with the allowed set — the caller surfaces a\n * named refusal (via {@link formatModelNotAllowedRefusal}) rather than\n * silently downgrading to an arbitrary model. A different model is a\n * different decision-maker; the product's fallback ORDER is a product\n * decision, and this module only executes it.\n */\n\n/** Parse the allowed-model set out of a router `model_not_allowed` refusal.\n * Both refusal shapes name the set after `Allowed:` —\n * `Model \"x\" is not in this key's allow-list. Allowed: a, b, c.` and\n * `Fallback model(s) \"x\" are not in this key's allow-list. Allowed: a, b.`\n * Returns null when the message carries no set (an unexpected shape — do\n * not guess). */\nexport function parseModelNotAllowed(message: string): string[] | null {\n const match = /Allowed:\\s*([^\\n]+?)(?:\\.\\s*)?$/.exec(message)\n if (!match?.[1]) return null\n const allowed = match[1].split(',').map((id) => id.trim().replace(/^[\"']|[\"']$/g, '')).filter(Boolean)\n return allowed.length > 0 ? allowed : null\n}\n\nexport interface ResolveModelWithinAllowListInput {\n /** The model the product wants to run. */\n readonly requested: string\n /** The key's allowed-model set (empty/absent = the key is unscoped; the\n * request stands). */\n readonly allowedModels?: readonly string[] | null\n /** The product's reroute order when `requested` is blocked — tried in\n * order, first allowed wins. */\n readonly fallbacks?: readonly string[]\n}\n\nexport type ModelWithinAllowListResolution =\n | { readonly status: 'allowed'; readonly model: string; readonly rerouted: false }\n | { readonly status: 'rerouted'; readonly model: string; readonly rerouted: true; readonly blockedModel: string }\n | { readonly status: 'refused'; readonly model: null; readonly allowedModels: readonly string[] }\n\n/** Resolve a model request against the key's allow-list:\n * unscoped → allowed as-is; requested permitted → allowed; a fallback\n * permitted → rerouted (named); nothing permitted → refused with the set. */\nexport function resolveModelWithinAllowList(input: ResolveModelWithinAllowListInput): ModelWithinAllowListResolution {\n const allowed = input.allowedModels\n if (!allowed || allowed.length === 0) {\n return { status: 'allowed', model: input.requested, rerouted: false }\n }\n if (allowed.includes(input.requested)) {\n return { status: 'allowed', model: input.requested, rerouted: false }\n }\n const fallback = (input.fallbacks ?? []).find((id) => allowed.includes(id))\n if (fallback !== undefined) {\n return { status: 'rerouted', model: fallback, rerouted: true, blockedModel: input.requested }\n }\n return { status: 'refused', model: null, allowedModels: allowed }\n}\n\n/** Format the loud, correctable refusal: the blocked model, the allowed set,\n * and the fix. Mirrors the router's own refusal text so both layers name\n * the same fact. */\nexport function formatModelNotAllowedRefusal(input: {\n requested: string\n allowedModels: readonly string[]\n}): string {\n return (\n `Model \"${input.requested}\" is not in this key's allow-list. ` +\n `Allowed: ${input.allowedModels.join(', ')}. ` +\n 'Route to an allowed model or widen the key\\u2019s allow-list.'\n )\n}\n","/**\n * Canonical chat-model resolution — identical across every agent app.\n *\n * The ONLY per-app inputs are DATA, never logic: the default model, the\n * allowlist, the env value the deployment set, and the catalog-fetch loader.\n * The logic is one precedence ladder + one fail-closed validator that every\n * product uses the same way — there is no per-product variant, no env-var name\n * baked in, and no backend dimension (router-vs-sandbox is the harness/dispatch\n * concern, not model resolution; a sandbox's provider default lives in the\n * sandbox subpath).\n *\n * - resolveChatModel: request > workspace > env > default. The product reads its\n * own deploy env var and passes the VALUE as `envModel`; the shell knows no\n * env-var names. Source is canonical: 'request' | 'workspace' | 'env' | 'default'.\n * - validateChatModelId: fail-closed. Admit an id that is in the allowlist, or\n * equals the operator-set env model, or is served by the live router catalog\n * (exact, or a bare id resolved to its canonical id when the suffix is unique).\n */\n\n/** The router /v1/models entry shape this module reads. Minimal on purpose. */\nexport interface ModelInfo {\n id: string\n name?: string\n _provider?: string\n provider?: string\n}\n\n/** Canonical (provider-prefixed) id for a catalog entry: pass through an id that\n * already carries a provider, else prefix the entry's provider when present. */\nfunction canonicalModelId(model: ModelInfo): string {\n if (model.id.includes('/')) return model.id\n const provider = model._provider ?? model.provider\n return provider ? `${provider}/${model.id}` : model.id\n}\n\n/** Define possible origins for the chat model configuration values */\nexport type ChatModelSource = 'request' | 'workspace' | 'env' | 'default'\n\n/** Resolve a chat model with its identifier and source information */\nexport interface ResolvedChatModel {\n model: string\n source: ChatModelSource\n}\n\n/** Represent successful chat model validation with a true status and a validated string value */\nexport interface ChatModelValidationSuccess {\n succeeded: true\n value: string\n}\n\n/** Describe a failed chat model validation result with an error message */\nexport interface ChatModelValidationFailure {\n succeeded: false\n error: string\n}\n\n/** Resolve the outcome of validating a chat model as either success or failure */\nexport type ChatModelValidationResult = ChatModelValidationSuccess | ChatModelValidationFailure\n\n/** The catalog-fetch boundary: maps a router base URL to the raw model list. */\nexport type LoadModels = (routerBaseUrl: string) => Promise<ModelInfo[]>\n\n/** Resolve the effective chat model input by prioritizing request, workspace, environment, and default models */\nexport interface ResolveChatModelInput {\n /** Per-request override (highest precedence). */\n requestModel?: string\n /** Persisted workspace-pinned model. */\n workspaceModel?: string\n /** The value the deployment's model env var holds (the product reads its own\n * var name and passes the value — the shell stays env-var-name agnostic). */\n envModel?: string\n /** Final fallback (the product's default, typically profile.model.default). */\n defaultModel: string\n}\n\n/** Resolve the chat-turn model by the one canonical precedence. Blank values are\n * treated as absent. */\nexport function resolveChatModel(input: ResolveChatModelInput): ResolvedChatModel {\n const request = cleanModelId(input.requestModel)\n if (request) return { model: request, source: 'request' }\n const workspace = cleanModelId(input.workspaceModel)\n if (workspace) return { model: workspace, source: 'workspace' }\n const env = cleanModelId(input.envModel)\n if (env) return { model: env, source: 'env' }\n return { model: input.defaultModel, source: 'default' }\n}\n\n/** Define input parameters for validating chat model IDs with optional allowlist and catalog access details */\nexport interface ValidateChatModelIdInput {\n /** Ids accepted without a catalog round-trip (defaults + operator-trusted). */\n allowlist?: Iterable<string>\n /** The operator-set env model value — always admitted (operator-trusted). */\n envModel?: string\n /** Catalog loader; required to reach the catalog path. */\n loadModels?: LoadModels\n /** Catalog endpoint base; required to reach the catalog path. */\n routerBaseUrl?: string\n}\n\n/**\n * Fail-closed model-id validation. Accepts an id only when it is well-formed AND\n * (in the allowlist, or equals the operator-set env model, or served by the live\n * catalog). A bare id (no provider prefix) resolves to its canonical id only when\n * the suffix is unique across the catalog — an ambiguous suffix is rejected\n * rather than silently assigned a provider.\n */\nexport async function validateChatModelId(\n modelId: unknown,\n input: ValidateChatModelIdInput,\n): Promise<ChatModelValidationResult> {\n const cleaned = cleanModelId(modelId)\n if (!cleaned) return { succeeded: false, error: 'Model id must be a non-empty string.' }\n if (!isWellFormedModelId(cleaned)) return { succeeded: false, error: `Model id is malformed: ${cleaned}` }\n\n const allowed = new Set(input.allowlist ?? [])\n if (allowed.has(cleaned)) return { succeeded: true, value: cleaned }\n\n // The operator-set env model is trusted without a catalog round-trip.\n if (cleanModelId(input.envModel) === cleaned) return { succeeded: true, value: cleaned }\n\n if (!input.loadModels || typeof input.routerBaseUrl !== 'string' || input.routerBaseUrl.length === 0) {\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n }\n\n let catalog: ModelInfo[]\n try {\n catalog = await input.loadModels(input.routerBaseUrl)\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err)\n return { succeeded: false, error: `Could not validate model catalog: ${message}` }\n }\n\n const ids = new Set(catalog.flatMap(catalogIdsForModel))\n if (ids.has(cleaned)) return { succeeded: true, value: cleaned }\n\n if (!cleaned.includes('/')) {\n const canonicalBySuffix = new Map<string, string[]>()\n for (const model of catalog) {\n if (typeof model.id !== 'string' || !model.id.trim()) continue\n const canonical = canonicalModelId(model)\n if (!canonical.includes('/')) continue\n const suffix = canonical.split('/').slice(1).join('/')\n const entries = canonicalBySuffix.get(suffix)\n if (entries) entries.push(canonical)\n else canonicalBySuffix.set(suffix, [canonical])\n }\n const matches = canonicalBySuffix.get(cleaned)\n if (matches && matches.length === 1) return { succeeded: true, value: matches[0]! }\n }\n\n return { succeeded: false, error: `Model is not available: ${cleaned}` }\n}\n\n/** Resolve and return a trimmed string model ID or undefined for invalid or empty input */\nexport function cleanModelId(value: unknown): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n return trimmed.length > 0 ? trimmed : undefined\n}\n\n/** Validate if a model ID string conforms to length and character format requirements */\nexport function isWellFormedModelId(modelId: string): boolean {\n if (modelId.length > 200) return false\n return /^[A-Za-z0-9._/@:-]+$/.test(modelId)\n}\n\n/** Resolve unique catalog IDs associated with a given model including its canonical form if applicable */\nexport function catalogIdsForModel(model: ModelInfo): string[] {\n const ids = new Set<string>()\n if (typeof model.id === 'string' && model.id.trim()) ids.add(model.id.trim())\n if (typeof model.id === 'string' && model.id.trim() && !model.id.includes('/')) {\n const canonical = canonicalModelId(model)\n if (canonical.includes('/')) ids.add(canonical)\n }\n return [...ids]\n}\n\n// Live model routing by allow-list: the positive form of model_not_allowed.\n// Parse the set the router's refusal already names, reroute within the\n// product's fallback order, or refuse loudly naming the set.\nexport {\n parseModelNotAllowed,\n resolveModelWithinAllowList,\n formatModelNotAllowedRefusal,\n type ResolveModelWithinAllowListInput,\n type ModelWithinAllowListResolution,\n} from './allow-list'\n\n// Reactive failover for the case validation cannot catch: a model that IS in the\n// catalog but whose upstream is down. Catalog membership is not liveness.\nexport {\n isUpstreamUnavailable,\n readHttpStatusHint,\n runWithModelFailover,\n buildModelChain,\n ModelFailoverExhaustedError,\n UPSTREAM_UNAVAILABLE_CODES,\n UPSTREAM_UNAVAILABLE_STATUSES,\n type ModelFailoverAttempt,\n type ModelFailoverResult,\n type RunWithModelFailoverInput,\n} from './failover'\n"],"mappings":"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","names":[]}
|
package/dist/profile/index.js
CHANGED