Merge pull request #74 from multica-ai/feat/connection-code
feat: connection code flow with cross-platform Chat
This commit is contained in:
commit
91044dea0f
14 changed files with 412 additions and 267 deletions
|
|
@ -13,13 +13,15 @@
|
|||
"@hugeicons/core-free-icons": "^3.1.1",
|
||||
"@hugeicons/react": "^1.1.4",
|
||||
"@multica/sdk": "workspace:*",
|
||||
"@multica/store": "workspace:*",
|
||||
"@multica/ui": "workspace:*",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:",
|
||||
"react-router-dom": "^7.13.0",
|
||||
"socket.io-client": "^4.8.3",
|
||||
"uuid": "^13.0.0"
|
||||
"uuid": "^13.0.0",
|
||||
"zustand": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,5 @@
|
|||
import { useNavigate } from 'react-router-dom'
|
||||
import { Button } from '@multica/ui/components/ui/button'
|
||||
import { Chat } from '@multica/ui/components/chat'
|
||||
|
||||
export default function ChatPage() {
|
||||
const navigate = useNavigate()
|
||||
|
||||
return (
|
||||
<div className="flex h-screen flex-col items-center justify-center gap-4">
|
||||
<h1 className="text-2xl font-bold">Chat</h1>
|
||||
<Button variant="outline" onClick={() => navigate('/')}>
|
||||
Back to Home
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
return <Chat />
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { Outlet, NavLink, useLocation } from 'react-router-dom'
|
||||
import { useHubInit, useGatewayStore, useHubStore, clearConnection } from '@multica/store'
|
||||
import { Toaster } from '@multica/ui/components/ui/sonner'
|
||||
import { Button } from '@multica/ui/components/ui/button'
|
||||
import { HugeiconsIcon } from '@hugeicons/react'
|
||||
import {
|
||||
|
|
@ -18,8 +20,20 @@ const tabs = [
|
|||
]
|
||||
|
||||
export default function Layout() {
|
||||
useHubInit()
|
||||
const location = useLocation()
|
||||
|
||||
const gwState = useGatewayStore((s) => s.connectionState)
|
||||
const hubId = useGatewayStore((s) => s.hubId)
|
||||
const activeAgentId = useHubStore((s) => s.activeAgentId)
|
||||
const isConnected = gwState === 'registered' && !!hubId && !!activeAgentId
|
||||
|
||||
const handleDisconnect = () => {
|
||||
useGatewayStore.getState().disconnect()
|
||||
useHubStore.getState().reset()
|
||||
clearConnection()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-dvh flex flex-col bg-background">
|
||||
{/* Header */}
|
||||
|
|
@ -27,9 +41,21 @@ export default function Layout() {
|
|||
<div className="flex items-center gap-2">
|
||||
<span className="text-lg font-semibold">Multica</span>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon">
|
||||
<HugeiconsIcon icon={Settings02Icon} className="size-5" />
|
||||
</Button>
|
||||
<div className="flex items-center gap-1">
|
||||
{isConnected && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleDisconnect}
|
||||
className="text-xs text-muted-foreground"
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="ghost" size="icon">
|
||||
<HugeiconsIcon icon={Settings02Icon} className="size-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* Tabs */}
|
||||
|
|
@ -55,9 +81,10 @@ export default function Layout() {
|
|||
</nav>
|
||||
|
||||
{/* Content */}
|
||||
<main className="flex-1 overflow-auto p-4">
|
||||
<main className={cn('flex-1 overflow-auto', location.pathname === '/chat' ? '' : 'p-4')}>
|
||||
<Outlet />
|
||||
</main>
|
||||
<Toaster />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
54
apps/web/app/app-header.tsx
Normal file
54
apps/web/app/app-header.tsx
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
useHubInit,
|
||||
useGatewayStore,
|
||||
useHubStore,
|
||||
clearConnection,
|
||||
} from "@multica/store";
|
||||
import { Button } from "@multica/ui/components/ui/button";
|
||||
import { ThemeToggle } from "./theme-toggle";
|
||||
|
||||
export function AppHeader({ children }: { children: React.ReactNode }) {
|
||||
useHubInit();
|
||||
|
||||
const gwState = useGatewayStore((s) => s.connectionState);
|
||||
const hubId = useGatewayStore((s) => s.hubId);
|
||||
const activeAgentId = useHubStore((s) => s.activeAgentId);
|
||||
const isConnected = gwState === "registered" && !!hubId && !!activeAgentId;
|
||||
|
||||
const handleDisconnect = () => {
|
||||
useGatewayStore.getState().disconnect();
|
||||
useHubStore.getState().reset();
|
||||
clearConnection();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<header>
|
||||
<div className="flex items-center justify-between px-4 py-2 max-w-4xl mx-auto">
|
||||
<div className="flex items-center gap-2.5">
|
||||
<img src="/icon.png" alt="Multica" className="size-6 rounded-md" />
|
||||
<span className="text-sm tracking-wide font-[family-name:var(--font-brand)]">
|
||||
Multica
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<ThemeToggle />
|
||||
{isConnected && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleDisconnect}
|
||||
className="text-xs text-muted-foreground"
|
||||
>
|
||||
Disconnect
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,22 +1,11 @@
|
|||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono, Inter, Playfair_Display } from "next/font/google";
|
||||
import { useGatewayStore } from "@multica/store";
|
||||
import "@multica/ui/globals.css";
|
||||
import {
|
||||
SidebarProvider,
|
||||
SidebarInset,
|
||||
} from "@multica/ui/components/ui/sidebar";
|
||||
import { AppSidebar } from "@multica/ui/components/app-sidebar";
|
||||
import { ThemeProvider } from "@multica/ui/components/theme-provider";
|
||||
import { AppHeader } from "./app-header";
|
||||
import { Toaster } from "@multica/ui/components/ui/sonner";
|
||||
import { HubSidebar } from "@multica/ui/components/hub-sidebar";
|
||||
import { ServiceWorkerRegister } from "./sw-register";
|
||||
|
||||
const gatewayUrl = process.env.NEXT_PUBLIC_GATEWAY_URL;
|
||||
if (gatewayUrl) {
|
||||
useGatewayStore.getState().setGatewayUrl(gatewayUrl);
|
||||
}
|
||||
|
||||
const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });
|
||||
|
||||
const geistSans = Geist({
|
||||
|
|
@ -56,7 +45,7 @@ export default function RootLayout({
|
|||
return (
|
||||
<html lang="en" className={inter.variable} suppressHydrationWarning>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} ${playfair.variable} antialiased`}
|
||||
className={`${geistSans.variable} ${geistMono.variable} ${playfair.variable} antialiased h-dvh flex flex-col`}
|
||||
>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
|
|
@ -64,14 +53,9 @@ export default function RootLayout({
|
|||
enableSystem
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<SidebarProvider>
|
||||
<AppSidebar>
|
||||
<HubSidebar />
|
||||
</AppSidebar>
|
||||
<SidebarInset>
|
||||
<div className="flex h-dvh overflow-hidden">{children}</div>
|
||||
</SidebarInset>
|
||||
</SidebarProvider>
|
||||
<AppHeader>
|
||||
<div className="flex-1 overflow-hidden">{children}</div>
|
||||
</AppHeader>
|
||||
</ThemeProvider>
|
||||
<Toaster />
|
||||
<ServiceWorkerRegister />
|
||||
|
|
|
|||
22
apps/web/app/theme-toggle.tsx
Normal file
22
apps/web/app/theme-toggle.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
"use client";
|
||||
|
||||
import { useTheme } from "next-themes";
|
||||
import { Button } from "@multica/ui/components/ui/button";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { Sun01Icon, Moon01Icon } from "@hugeicons/core-free-icons";
|
||||
|
||||
export function ThemeToggle() {
|
||||
const { theme, setTheme } = useTheme();
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setTheme(theme === "dark" ? "light" : "dark")}
|
||||
className="size-8 text-muted-foreground"
|
||||
>
|
||||
<HugeiconsIcon icon={Sun01Icon} strokeWidth={1.5} className="size-4 dark:hidden" />
|
||||
<HugeiconsIcon icon={Moon01Icon} strokeWidth={1.5} className="size-4 hidden dark:block" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@
|
|||
"@hugeicons/core-free-icons": "^3.1.1",
|
||||
"@hugeicons/react": "^1.1.4",
|
||||
"next": "16.1.6",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "catalog:",
|
||||
"react-dom": "catalog:"
|
||||
},
|
||||
|
|
|
|||
116
packages/store/src/connection.ts
Normal file
116
packages/store/src/connection.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
const STORAGE_KEY = "multica-connection"
|
||||
|
||||
export interface ConnectionInfo {
|
||||
type: "multica-connect"
|
||||
gateway: string
|
||||
hubId: string
|
||||
agentId: string
|
||||
token: string
|
||||
expires: number
|
||||
}
|
||||
|
||||
function isConnectionInfo(obj: unknown): obj is ConnectionInfo {
|
||||
if (typeof obj !== "object" || obj === null) return false
|
||||
const o = obj as Record<string, unknown>
|
||||
return (
|
||||
o.type === "multica-connect" &&
|
||||
typeof o.gateway === "string" &&
|
||||
typeof o.hubId === "string" &&
|
||||
typeof o.agentId === "string" &&
|
||||
typeof o.token === "string" &&
|
||||
typeof o.expires === "number"
|
||||
)
|
||||
}
|
||||
|
||||
// Parse multica://connect?gateway=...&hub=...&agent=...&token=...&exp=... URL format
|
||||
// Uses string prefix + URLSearchParams to avoid cross-engine URL hostname differences
|
||||
function parseConnectionUrl(input: string): ConnectionInfo | null {
|
||||
const prefix = "multica://connect?"
|
||||
if (!input.startsWith(prefix)) return null
|
||||
try {
|
||||
const params = new URLSearchParams(input.slice(prefix.length))
|
||||
const gateway = params.get("gateway")
|
||||
const hubId = params.get("hub")
|
||||
const agentId = params.get("agent")
|
||||
const token = params.get("token")
|
||||
const exp = params.get("exp")
|
||||
if (!gateway || !hubId || !agentId || !token || !exp) return null
|
||||
return {
|
||||
type: "multica-connect",
|
||||
gateway,
|
||||
hubId,
|
||||
agentId,
|
||||
token,
|
||||
expires: Number(exp),
|
||||
}
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function isExpired(expires: number): boolean {
|
||||
// Desktop generates expires as millisecond timestamp (Date.now() + seconds * 1000)
|
||||
return Date.now() > expires
|
||||
}
|
||||
|
||||
export function parseConnectionCode(input: string): ConnectionInfo {
|
||||
const trimmed = input.trim()
|
||||
|
||||
// Try multica:// URL format first (desktop "Copy Link" output)
|
||||
const fromUrl = parseConnectionUrl(trimmed)
|
||||
if (fromUrl) {
|
||||
if (isExpired(fromUrl.expires)) {
|
||||
throw new Error("Connection code has expired")
|
||||
}
|
||||
return fromUrl
|
||||
}
|
||||
|
||||
// Try JSON (QR code scan output)
|
||||
let parsed: unknown
|
||||
try {
|
||||
parsed = JSON.parse(trimmed)
|
||||
} catch {
|
||||
// Try base64 decode then JSON
|
||||
try {
|
||||
parsed = JSON.parse(atob(trimmed))
|
||||
} catch {
|
||||
throw new Error("Invalid connection code")
|
||||
}
|
||||
}
|
||||
|
||||
if (!isConnectionInfo(parsed)) {
|
||||
throw new Error("Invalid connection code format")
|
||||
}
|
||||
|
||||
if (isExpired(parsed.expires)) {
|
||||
throw new Error("Connection code has expired")
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
|
||||
export function saveConnection(info: ConnectionInfo): void {
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(info))
|
||||
}
|
||||
|
||||
export function loadConnection(): ConnectionInfo | null {
|
||||
const raw = localStorage.getItem(STORAGE_KEY)
|
||||
if (!raw) return null
|
||||
|
||||
try {
|
||||
const info = JSON.parse(raw)
|
||||
if (!isConnectionInfo(info)) return null
|
||||
if (isExpired(info.expires)) {
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
return null
|
||||
}
|
||||
return info
|
||||
} catch {
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function clearConnection(): void {
|
||||
localStorage.removeItem(STORAGE_KEY)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { create } from "zustand"
|
||||
import { GatewayClient, StreamAction, extractTextFromEvent, type ConnectionState, type DeviceInfo, type SendErrorResponse, type StreamPayload, type StreamMessageEvent } from "@multica/sdk"
|
||||
import { useMessagesStore } from "./messages"
|
||||
import type { ConnectionInfo } from "./connection"
|
||||
|
||||
const DEFAULT_GATEWAY_URL = "http://localhost:3000"
|
||||
|
||||
|
|
@ -8,6 +9,7 @@ interface GatewayState {
|
|||
gatewayUrl: string
|
||||
connectionState: ConnectionState
|
||||
hubId: string | null
|
||||
agentId: string | null
|
||||
hubs: DeviceInfo[]
|
||||
lastError: SendErrorResponse | null
|
||||
}
|
||||
|
|
@ -15,6 +17,7 @@ interface GatewayState {
|
|||
interface GatewayActions {
|
||||
setGatewayUrl: (url: string) => void
|
||||
connect: (deviceId: string) => void
|
||||
connectWithCode: (code: ConnectionInfo, deviceId: string) => void
|
||||
disconnect: () => void
|
||||
setHubId: (hubId: string) => void
|
||||
listDevices: () => Promise<DeviceInfo[]>
|
||||
|
|
@ -26,10 +29,56 @@ export type GatewayStore = GatewayState & GatewayActions
|
|||
|
||||
let client: GatewayClient | null = null
|
||||
|
||||
function createClient(url: string, deviceId: string, set: (s: Partial<GatewayState>) => void): GatewayClient {
|
||||
return new GatewayClient({
|
||||
url,
|
||||
deviceId,
|
||||
deviceType: "client",
|
||||
})
|
||||
.onStateChange((connectionState) => set({ connectionState }))
|
||||
.onMessage((msg) => {
|
||||
if (msg.action === StreamAction) {
|
||||
const payload = msg.payload as StreamPayload
|
||||
const store = useMessagesStore.getState()
|
||||
const { event } = payload
|
||||
|
||||
switch (event.type) {
|
||||
case "message_start": {
|
||||
store.startStream(payload.streamId, payload.agentId)
|
||||
const text = extractTextFromEvent(event as StreamMessageEvent)
|
||||
if (text) store.appendStream(payload.streamId, text)
|
||||
break
|
||||
}
|
||||
case "message_update": {
|
||||
const text = extractTextFromEvent(event as StreamMessageEvent)
|
||||
store.appendStream(payload.streamId, text)
|
||||
break
|
||||
}
|
||||
case "message_end": {
|
||||
const text = extractTextFromEvent(event as StreamMessageEvent)
|
||||
store.endStream(payload.streamId, text)
|
||||
break
|
||||
}
|
||||
case "tool_execution_start":
|
||||
case "tool_execution_end":
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const payload = msg.payload as { agentId?: string; content?: string }
|
||||
if (payload?.agentId && payload?.content) {
|
||||
useMessagesStore.getState().addAssistantMessage(payload.content, payload.agentId)
|
||||
}
|
||||
})
|
||||
.onSendError((error) => set({ lastError: error }))
|
||||
}
|
||||
|
||||
export const useGatewayStore = create<GatewayStore>()((set, get) => ({
|
||||
gatewayUrl: DEFAULT_GATEWAY_URL,
|
||||
connectionState: "disconnected",
|
||||
hubId: null,
|
||||
agentId: null,
|
||||
hubs: [],
|
||||
lastError: null,
|
||||
|
||||
|
|
@ -37,53 +86,24 @@ export const useGatewayStore = create<GatewayStore>()((set, get) => ({
|
|||
|
||||
connect: (deviceId) => {
|
||||
if (client) return
|
||||
client = createClient(get().gatewayUrl, deviceId, set)
|
||||
client.connect()
|
||||
},
|
||||
|
||||
client = new GatewayClient({
|
||||
url: get().gatewayUrl,
|
||||
deviceId,
|
||||
deviceType: "client",
|
||||
connectWithCode: (code, deviceId) => {
|
||||
// Disconnect existing connection if any
|
||||
if (client) {
|
||||
client.disconnect()
|
||||
client = null
|
||||
}
|
||||
|
||||
set({
|
||||
gatewayUrl: code.gateway,
|
||||
hubId: code.hubId,
|
||||
agentId: code.agentId,
|
||||
})
|
||||
.onStateChange((connectionState) => set({ connectionState }))
|
||||
.onMessage((msg) => {
|
||||
// Handle streaming messages (new protocol: payload.event is a raw AgentEvent)
|
||||
if (msg.action === StreamAction) {
|
||||
const payload = msg.payload as StreamPayload
|
||||
const store = useMessagesStore.getState()
|
||||
const { event } = payload
|
||||
|
||||
switch (event.type) {
|
||||
case "message_start": {
|
||||
store.startStream(payload.streamId, payload.agentId)
|
||||
const text = extractTextFromEvent(event as StreamMessageEvent)
|
||||
if (text) store.appendStream(payload.streamId, text)
|
||||
break
|
||||
}
|
||||
case "message_update": {
|
||||
const text = extractTextFromEvent(event as StreamMessageEvent)
|
||||
store.appendStream(payload.streamId, text)
|
||||
break
|
||||
}
|
||||
case "message_end": {
|
||||
const text = extractTextFromEvent(event as StreamMessageEvent)
|
||||
store.endStream(payload.streamId, text)
|
||||
break
|
||||
}
|
||||
case "tool_execution_start":
|
||||
case "tool_execution_end":
|
||||
// TODO: surface tool execution status in UI
|
||||
break
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Fallback: complete message handling
|
||||
const payload = msg.payload as { agentId?: string; content?: string }
|
||||
if (payload?.agentId && payload?.content) {
|
||||
useMessagesStore.getState().addAssistantMessage(payload.content, payload.agentId)
|
||||
}
|
||||
})
|
||||
.onSendError((error) => set({ lastError: error }))
|
||||
|
||||
client = createClient(code.gateway, deviceId, set)
|
||||
client.connect()
|
||||
},
|
||||
|
||||
|
|
@ -92,7 +112,7 @@ export const useGatewayStore = create<GatewayStore>()((set, get) => ({
|
|||
client.disconnect()
|
||||
client = null
|
||||
}
|
||||
set({ connectionState: "disconnected", hubId: null, hubs: [] })
|
||||
set({ connectionState: "disconnected", hubId: null, agentId: null, hubs: [] })
|
||||
},
|
||||
|
||||
setHubId: (hubId) => set({ hubId }),
|
||||
|
|
|
|||
|
|
@ -4,37 +4,39 @@ import { useEffect } from "react"
|
|||
import { useHubStore } from "./hub"
|
||||
import { useDeviceId } from "./device-id"
|
||||
import { useGatewayStore } from "./gateway"
|
||||
import { loadConnection } from "./connection"
|
||||
|
||||
export function useHubInit() {
|
||||
const deviceId = useDeviceId()
|
||||
const gwState = useGatewayStore((s) => s.connectionState)
|
||||
const hubId = useGatewayStore((s) => s.hubId)
|
||||
const agentId = useGatewayStore((s) => s.agentId)
|
||||
const reset = useHubStore((s) => s.reset)
|
||||
const fetchHub = useHubStore((s) => s.fetchHub)
|
||||
const fetchAgents = useHubStore((s) => s.fetchAgents)
|
||||
const setActiveAgentId = useHubStore((s) => s.setActiveAgentId)
|
||||
|
||||
// Auto-connect WS when deviceId is available
|
||||
// Auto-connect from saved connection code
|
||||
useEffect(() => {
|
||||
if (deviceId) {
|
||||
useGatewayStore.getState().connect(deviceId)
|
||||
return () => { useGatewayStore.getState().disconnect() }
|
||||
if (!deviceId) return
|
||||
const saved = loadConnection()
|
||||
if (saved) {
|
||||
useGatewayStore.getState().connectWithCode(saved, deviceId)
|
||||
}
|
||||
return () => { useGatewayStore.getState().disconnect() }
|
||||
}, [deviceId])
|
||||
|
||||
// Once WS is registered, discover available hubs
|
||||
useEffect(() => {
|
||||
if (gwState === "registered") {
|
||||
useGatewayStore.getState().listDevices()
|
||||
}
|
||||
}, [gwState])
|
||||
|
||||
// Once hubId is set and WS is registered, fetch hub info and agents via RPC
|
||||
// Once registered with a hub, fetch hub info and agents, set active agent
|
||||
useEffect(() => {
|
||||
if (gwState === "registered" && hubId) {
|
||||
fetchHub()
|
||||
fetchAgents()
|
||||
if (agentId) {
|
||||
setActiveAgentId(agentId)
|
||||
}
|
||||
}
|
||||
if (gwState === "disconnected") {
|
||||
useHubStore.setState({ status: "idle", hub: null, agents: [], activeAgentId: null })
|
||||
reset()
|
||||
}
|
||||
}, [gwState, hubId, fetchHub, fetchAgents])
|
||||
}, [gwState, hubId, agentId, reset, fetchHub, fetchAgents, setActiveAgentId])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ interface HubState {
|
|||
}
|
||||
|
||||
interface HubActions {
|
||||
reset: () => void
|
||||
setActiveAgentId: (id: string | null) => void
|
||||
fetchHub: () => Promise<void>
|
||||
fetchAgents: () => Promise<void>
|
||||
|
|
@ -54,6 +55,8 @@ export const useHubStore = create<HubStore>()((set, get) => ({
|
|||
agents: [],
|
||||
activeAgentId: null,
|
||||
|
||||
reset: () => set({ status: "idle", hub: null, agents: [], activeAgentId: null }),
|
||||
|
||||
setActiveAgentId: (id) => {
|
||||
set({ activeAgentId: id })
|
||||
if (id) {
|
||||
|
|
|
|||
|
|
@ -6,3 +6,5 @@ export { useMessagesStore } from "./messages"
|
|||
export type { Message, MessagesStore } from "./messages"
|
||||
export { useGatewayStore } from "./gateway"
|
||||
export type { GatewayStore } from "./gateway"
|
||||
export { parseConnectionCode, saveConnection, loadConnection, clearConnection } from "./connection"
|
||||
export type { ConnectionInfo } from "./connection"
|
||||
|
|
|
|||
|
|
@ -1,36 +1,50 @@
|
|||
"use client";
|
||||
|
||||
import { useRef, useState, useCallback, useMemo } from "react";
|
||||
import { SidebarTrigger } from "@multica/ui/components/ui/sidebar";
|
||||
import { Badge } from "@multica/ui/components/ui/badge";
|
||||
import { Button } from "@multica/ui/components/ui/button";
|
||||
import { Textarea } from "@multica/ui/components/ui/textarea";
|
||||
import { ChatInput } from "@multica/ui/components/chat-input";
|
||||
import { MemoizedMarkdown } from "@multica/ui/components/markdown";
|
||||
import { StreamingMarkdown } from "@multica/ui/components/markdown/StreamingMarkdown";
|
||||
import { HugeiconsIcon } from "@hugeicons/react";
|
||||
import { UserIcon, Copy01Icon, CheckmarkCircle02Icon } from "@hugeicons/core-free-icons";
|
||||
import { toast } from "@multica/ui/components/ui/sonner";
|
||||
import { useHubStore, useDeviceId, useMessagesStore, useGatewayStore } from "@multica/store";
|
||||
import {
|
||||
useHubStore,
|
||||
useMessagesStore,
|
||||
useGatewayStore,
|
||||
useDeviceId,
|
||||
parseConnectionCode,
|
||||
saveConnection,
|
||||
} from "@multica/store";
|
||||
import { useScrollFade } from "@multica/ui/hooks/use-scroll-fade";
|
||||
import { useAutoScroll } from "@multica/ui/hooks/use-auto-scroll";
|
||||
import { Skeleton } from "@multica/ui/components/ui/skeleton";
|
||||
import { cn } from "@multica/ui/lib/utils";
|
||||
|
||||
const STATE_VARIANT: Record<string, "default" | "secondary" | "destructive" | "outline"> = {
|
||||
registered: "default",
|
||||
connected: "secondary",
|
||||
connecting: "secondary",
|
||||
disconnected: "destructive",
|
||||
}
|
||||
|
||||
export function Chat() {
|
||||
const deviceId = useDeviceId()
|
||||
const activeAgentId = useHubStore((s) => s.activeAgentId)
|
||||
const gwState = useGatewayStore((s) => s.connectionState)
|
||||
const hubId = useGatewayStore((s) => s.hubId)
|
||||
|
||||
const messages = useMessagesStore((s) => s.messages)
|
||||
const streamingIds = useMessagesStore((s) => s.streamingIds)
|
||||
const filtered = useMemo(() => messages.filter(m => m.agentId === activeAgentId), [messages, activeAgentId])
|
||||
|
||||
const isConnected = gwState === "registered" && !!hubId && !!activeAgentId
|
||||
const [codeInput, setCodeInput] = useState("")
|
||||
|
||||
const handleConnect = useCallback(() => {
|
||||
const trimmed = codeInput.trim()
|
||||
if (!trimmed || !deviceId) return
|
||||
try {
|
||||
const info = parseConnectionCode(trimmed)
|
||||
saveConnection(info)
|
||||
useGatewayStore.getState().connectWithCode(info, deviceId)
|
||||
setCodeInput("")
|
||||
} catch (e) {
|
||||
toast.error((e as Error).message)
|
||||
}
|
||||
}, [codeInput, deviceId])
|
||||
|
||||
const handleSend = useCallback((text: string) => {
|
||||
const { hubId } = useGatewayStore.getState()
|
||||
const agentId = useHubStore.getState().activeAgentId
|
||||
|
|
@ -39,61 +53,43 @@ export function Chat() {
|
|||
useGatewayStore.getState().send(hubId, "message", { agentId, content: text })
|
||||
}, [])
|
||||
|
||||
const canSend = gwState === "registered" && !!activeAgentId
|
||||
|
||||
const deviceId = useDeviceId()
|
||||
const [deviceCopied, setDeviceCopied] = useState(false)
|
||||
const handleCopyDevice = useCallback(async () => {
|
||||
if (!deviceId) return
|
||||
try {
|
||||
await navigator.clipboard.writeText(deviceId)
|
||||
setDeviceCopied(true)
|
||||
toast.success("Device ID copied")
|
||||
setTimeout(() => setDeviceCopied(false), 2000)
|
||||
} catch {
|
||||
toast.error("Failed to copy")
|
||||
}
|
||||
}, [deviceId])
|
||||
|
||||
const mainRef = useRef<HTMLElement>(null)
|
||||
const fadeStyle = useScrollFade(mainRef)
|
||||
useAutoScroll(mainRef)
|
||||
|
||||
return (
|
||||
<div className="h-dvh flex flex-col overflow-hidden w-full">
|
||||
<header className="flex items-center gap-2 p-2">
|
||||
<SidebarTrigger />
|
||||
{deviceId ? (
|
||||
<>
|
||||
<span className="text-xs text-muted-foreground font-mono">
|
||||
{deviceId}
|
||||
</span>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon-xs"
|
||||
onClick={handleCopyDevice}
|
||||
aria-label="Copy device ID"
|
||||
>
|
||||
<HugeiconsIcon
|
||||
icon={deviceCopied ? CheckmarkCircle02Icon : Copy01Icon}
|
||||
strokeWidth={2}
|
||||
className={cn("size-3", deviceCopied && "text-green-500")}
|
||||
/>
|
||||
</Button>
|
||||
</>
|
||||
) : (
|
||||
<Skeleton className="h-4 w-56" />
|
||||
)}
|
||||
<Badge variant={STATE_VARIANT[gwState] ?? "outline"} className="text-xs">
|
||||
{gwState}
|
||||
</Badge>
|
||||
</header>
|
||||
|
||||
<div className="h-full flex flex-col overflow-hidden w-full">
|
||||
<main ref={mainRef} className="flex-1 overflow-y-auto min-h-0" style={fadeStyle}>
|
||||
{!activeAgentId ? (
|
||||
<div className="flex flex-col items-center justify-center h-full gap-3 text-muted-foreground">
|
||||
<HugeiconsIcon icon={UserIcon} strokeWidth={1.5} className="size-10 opacity-30" />
|
||||
<span className="text-sm">Select an agent to start chatting</span>
|
||||
{!isConnected ? (
|
||||
<div className="flex flex-col items-center justify-center h-full gap-4 px-4">
|
||||
<div className="text-center space-y-1">
|
||||
<p className="text-sm text-muted-foreground">Paste a connection code to start</p>
|
||||
{(gwState === "connecting" || gwState === "connected") && (
|
||||
<p className="text-xs text-muted-foreground/60 animate-pulse">Connecting...</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-full max-w-sm space-y-3">
|
||||
<Textarea
|
||||
value={codeInput}
|
||||
onChange={(e) => setCodeInput(e.target.value)}
|
||||
placeholder="Paste connection code here..."
|
||||
className="text-xs font-mono min-h-[100px] resize-none"
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
||||
e.preventDefault()
|
||||
handleConnect()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="sm"
|
||||
onClick={handleConnect}
|
||||
disabled={!codeInput.trim() || gwState === "connecting"}
|
||||
className="w-full text-xs"
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : filtered.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
|
||||
|
|
@ -131,11 +127,12 @@ export function Chat() {
|
|||
)}
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="w-full p-2 pt-1 max-w-4xl mx-auto">
|
||||
<ChatInput
|
||||
onSubmit={handleSend}
|
||||
disabled={!canSend}
|
||||
placeholder={!activeAgentId ? "Select an agent first..." : "Type a message..."}
|
||||
disabled={!isConnected}
|
||||
placeholder={!isConnected ? "Connect first..." : "Type a message..."}
|
||||
/>
|
||||
</footer>
|
||||
</div>
|
||||
|
|
|
|||
137
pnpm-lock.yaml
generated
137
pnpm-lock.yaml
generated
|
|
@ -34,13 +34,13 @@ importers:
|
|||
dependencies:
|
||||
'@mariozechner/pi-agent-core':
|
||||
specifier: ^0.50.3
|
||||
version: 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6))(ws@8.18.3)(zod@4.3.6)
|
||||
version: 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))(ws@8.18.3)(zod@3.25.76)
|
||||
'@mariozechner/pi-ai':
|
||||
specifier: ^0.50.3
|
||||
version: 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6))(ws@8.18.3)(zod@4.3.6)
|
||||
version: 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))(ws@8.18.3)(zod@3.25.76)
|
||||
'@mariozechner/pi-coding-agent':
|
||||
specifier: ^0.50.3
|
||||
version: 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6))(ws@8.18.3)(zod@4.3.6)
|
||||
version: 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))(ws@8.18.3)(zod@3.25.76)
|
||||
'@mozilla/readability':
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0
|
||||
|
|
@ -156,6 +156,9 @@ importers:
|
|||
'@multica/sdk':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/sdk
|
||||
'@multica/store':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/store
|
||||
'@multica/ui':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/ui
|
||||
|
|
@ -177,6 +180,9 @@ importers:
|
|||
uuid:
|
||||
specifier: ^13.0.0
|
||||
version: 13.0.0
|
||||
zustand:
|
||||
specifier: 'catalog:'
|
||||
version: 5.0.10(@types/react@19.1.17)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3))
|
||||
devDependencies:
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.18
|
||||
|
|
@ -279,7 +285,7 @@ importers:
|
|||
version: 8.0.11(expo@54.0.33)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
expo-router:
|
||||
specifier: ~6.0.23
|
||||
version: 6.0.23(52130e04a12e6efd1d6e48b3f2ad01c3)
|
||||
version: 6.0.23(w7u2e3gmpia3npio76ytuzityu)
|
||||
expo-splash-screen:
|
||||
specifier: ~31.0.13
|
||||
version: 31.0.13(expo@54.0.33)
|
||||
|
|
@ -377,6 +383,9 @@ importers:
|
|||
next:
|
||||
specifier: 16.1.6
|
||||
version: 16.1.6(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
next-themes:
|
||||
specifier: ^0.4.6
|
||||
version: 0.4.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react:
|
||||
specifier: 'catalog:'
|
||||
version: 19.2.3
|
||||
|
|
@ -1862,105 +1871,89 @@ packages:
|
|||
resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-arm@1.2.4':
|
||||
resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-ppc64@1.2.4':
|
||||
resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-riscv64@1.2.4':
|
||||
resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-s390x@1.2.4':
|
||||
resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linux-x64@1.2.4':
|
||||
resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-arm64@1.2.4':
|
||||
resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-libvips-linuxmusl-x64@1.2.4':
|
||||
resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linux-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-arm@0.34.5':
|
||||
resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-ppc64@0.34.5':
|
||||
resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-riscv64@0.34.5':
|
||||
resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-s390x@0.34.5':
|
||||
resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linux-x64@0.34.5':
|
||||
resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@img/sharp-linuxmusl-arm64@0.34.5':
|
||||
resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-linuxmusl-x64@0.34.5':
|
||||
resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==}
|
||||
engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@img/sharp-wasm32@0.34.5':
|
||||
resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==}
|
||||
|
|
@ -2125,28 +2118,24 @@ packages:
|
|||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@mariozechner/clipboard-linux-riscv64-gnu@0.3.0':
|
||||
resolution: {integrity: sha512-4BC08CIaOXSSAGRZLEjqJmQfioED8ohAzwt0k2amZPEbH96YKoBNorq5EdwPf5VT+odS0DeyCwhwtxokRLZIvQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@mariozechner/clipboard-linux-x64-gnu@0.3.0':
|
||||
resolution: {integrity: sha512-GpNY5Y9nOzr0Vt0Qi5U88qwe6piiIHk44kSMexl8ns90LluN5UTNYmyfi7Xq3/lmPZCpnB2xvBTYbsXCxnopIA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@mariozechner/clipboard-linux-x64-musl@0.3.0':
|
||||
resolution: {integrity: sha512-+PnR48/x9GMY5Kh8BLjzHMx6trOegMtxAuqTM9X/bhV3QuW6sLLd7nojDHSGj/ZueK6i0tcQxvOrgNLozVtNDA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@mariozechner/clipboard-win32-arm64-msvc@0.3.0':
|
||||
resolution: {integrity: sha512-+dy2vZ1Ph4EYj0cotB+bVUVk/uKl2bh9LOp/zlnFqoCCYDN6sm+L0VyIOPPo3hjoEVdGpHe1MUxp3qG/OLwXgg==}
|
||||
|
|
@ -2308,28 +2297,24 @@ packages:
|
|||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-arm64-musl@16.1.6':
|
||||
resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-linux-x64-gnu@16.1.6':
|
||||
resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@next/swc-linux-x64-musl@16.1.6':
|
||||
resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@next/swc-win32-arm64-msvc@16.1.6':
|
||||
resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==}
|
||||
|
|
@ -2790,79 +2775,66 @@ packages:
|
|||
resolution: {integrity: sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.57.0':
|
||||
resolution: {integrity: sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.57.0':
|
||||
resolution: {integrity: sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.57.0':
|
||||
resolution: {integrity: sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.57.0':
|
||||
resolution: {integrity: sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.57.0':
|
||||
resolution: {integrity: sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.57.0':
|
||||
resolution: {integrity: sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.57.0':
|
||||
resolution: {integrity: sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.57.0':
|
||||
resolution: {integrity: sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.57.0':
|
||||
resolution: {integrity: sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.57.0':
|
||||
resolution: {integrity: sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.57.0':
|
||||
resolution: {integrity: sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.57.0':
|
||||
resolution: {integrity: sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.57.0':
|
||||
resolution: {integrity: sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==}
|
||||
|
|
@ -3187,28 +3159,24 @@ packages:
|
|||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
|
||||
resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
|
||||
resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
|
||||
resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
|
||||
resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
|
||||
|
|
@ -3557,49 +3525,41 @@ packages:
|
|||
resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-arm64-musl@1.11.1':
|
||||
resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@unrs/resolver-binding-linux-ppc64-gnu@1.11.1':
|
||||
resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-riscv64-gnu@1.11.1':
|
||||
resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-riscv64-musl@1.11.1':
|
||||
resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@unrs/resolver-binding-linux-s390x-gnu@1.11.1':
|
||||
resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-x64-gnu@1.11.1':
|
||||
resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@unrs/resolver-binding-linux-x64-musl@1.11.1':
|
||||
resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@unrs/resolver-binding-wasm32-wasi@1.11.1':
|
||||
resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==}
|
||||
|
|
@ -6285,56 +6245,48 @@ packages:
|
|||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.27.0:
|
||||
resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-arm64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.27.0:
|
||||
resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-gnu@1.30.2:
|
||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
lightningcss-linux-x64-musl@1.27.0:
|
||||
resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-linux-x64-musl@1.30.2:
|
||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
lightningcss-win32-arm64-msvc@1.27.0:
|
||||
resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==}
|
||||
|
|
@ -9128,11 +9080,11 @@ snapshots:
|
|||
package-manager-detector: 1.6.0
|
||||
tinyexec: 1.0.2
|
||||
|
||||
'@anthropic-ai/sdk@0.71.2(zod@4.3.6)':
|
||||
'@anthropic-ai/sdk@0.71.2(zod@3.25.76)':
|
||||
dependencies:
|
||||
json-schema-to-ts: 3.1.1
|
||||
optionalDependencies:
|
||||
zod: 4.3.6
|
||||
zod: 3.25.76
|
||||
|
||||
'@aws-crypto/crc32@5.2.0':
|
||||
dependencies:
|
||||
|
|
@ -10656,7 +10608,7 @@ snapshots:
|
|||
wrap-ansi: 7.0.0
|
||||
ws: 8.18.3
|
||||
optionalDependencies:
|
||||
expo-router: 6.0.23(52130e04a12e6efd1d6e48b3f2ad01c3)
|
||||
expo-router: 6.0.23(w7u2e3gmpia3npio76ytuzityu)
|
||||
react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
|
|
@ -10905,12 +10857,12 @@ snapshots:
|
|||
|
||||
'@floating-ui/utils@0.2.10': {}
|
||||
|
||||
'@google/genai@1.34.0(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6))':
|
||||
'@google/genai@1.34.0(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))':
|
||||
dependencies:
|
||||
google-auth-library: 10.5.0
|
||||
ws: 8.18.3
|
||||
optionalDependencies:
|
||||
'@modelcontextprotocol/sdk': 1.25.3(hono@4.11.7)(zod@4.3.6)
|
||||
'@modelcontextprotocol/sdk': 1.25.3(hono@4.11.7)(zod@3.25.76)
|
||||
transitivePeerDependencies:
|
||||
- bufferutil
|
||||
- supports-color
|
||||
|
|
@ -11245,9 +11197,9 @@ snapshots:
|
|||
std-env: 3.10.0
|
||||
yoctocolors: 2.1.2
|
||||
|
||||
'@mariozechner/pi-agent-core@0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6))(ws@8.18.3)(zod@4.3.6)':
|
||||
'@mariozechner/pi-agent-core@0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))(ws@8.18.3)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@mariozechner/pi-ai': 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6))(ws@8.18.3)(zod@4.3.6)
|
||||
'@mariozechner/pi-ai': 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))(ws@8.18.3)(zod@3.25.76)
|
||||
'@mariozechner/pi-tui': 0.50.3
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
|
|
@ -11258,21 +11210,21 @@ snapshots:
|
|||
- ws
|
||||
- zod
|
||||
|
||||
'@mariozechner/pi-ai@0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6))(ws@8.18.3)(zod@4.3.6)':
|
||||
'@mariozechner/pi-ai@0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))(ws@8.18.3)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@anthropic-ai/sdk': 0.71.2(zod@4.3.6)
|
||||
'@anthropic-ai/sdk': 0.71.2(zod@3.25.76)
|
||||
'@aws-sdk/client-bedrock-runtime': 3.978.0
|
||||
'@google/genai': 1.34.0(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6))
|
||||
'@google/genai': 1.34.0(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))
|
||||
'@mistralai/mistralai': 1.10.0
|
||||
'@sinclair/typebox': 0.34.48
|
||||
ajv: 8.17.1
|
||||
ajv-formats: 3.0.1(ajv@8.17.1)
|
||||
chalk: 5.6.2
|
||||
openai: 6.10.0(ws@8.18.3)(zod@4.3.6)
|
||||
openai: 6.10.0(ws@8.18.3)(zod@3.25.76)
|
||||
partial-json: 0.1.7
|
||||
proxy-agent: 6.5.0
|
||||
undici: 7.19.2
|
||||
zod-to-json-schema: 3.25.1(zod@4.3.6)
|
||||
zod-to-json-schema: 3.25.1(zod@3.25.76)
|
||||
transitivePeerDependencies:
|
||||
- '@modelcontextprotocol/sdk'
|
||||
- aws-crt
|
||||
|
|
@ -11282,12 +11234,12 @@ snapshots:
|
|||
- ws
|
||||
- zod
|
||||
|
||||
'@mariozechner/pi-coding-agent@0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6))(ws@8.18.3)(zod@4.3.6)':
|
||||
'@mariozechner/pi-coding-agent@0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))(ws@8.18.3)(zod@3.25.76)':
|
||||
dependencies:
|
||||
'@mariozechner/clipboard': 0.3.0
|
||||
'@mariozechner/jiti': 2.6.5
|
||||
'@mariozechner/pi-agent-core': 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6))(ws@8.18.3)(zod@4.3.6)
|
||||
'@mariozechner/pi-ai': 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6))(ws@8.18.3)(zod@4.3.6)
|
||||
'@mariozechner/pi-agent-core': 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))(ws@8.18.3)(zod@3.25.76)
|
||||
'@mariozechner/pi-ai': 0.50.3(@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@3.25.76))(ws@8.18.3)(zod@3.25.76)
|
||||
'@mariozechner/pi-tui': 0.50.3
|
||||
'@silvia-odwyer/photon-node': 0.3.4
|
||||
chalk: 5.6.2
|
||||
|
|
@ -11345,29 +11297,6 @@ snapshots:
|
|||
- hono
|
||||
- supports-color
|
||||
|
||||
'@modelcontextprotocol/sdk@1.25.3(hono@4.11.7)(zod@4.3.6)':
|
||||
dependencies:
|
||||
'@hono/node-server': 1.19.9(hono@4.11.7)
|
||||
ajv: 8.17.1
|
||||
ajv-formats: 3.0.1(ajv@8.17.1)
|
||||
content-type: 1.0.5
|
||||
cors: 2.8.6
|
||||
cross-spawn: 7.0.6
|
||||
eventsource: 3.0.7
|
||||
eventsource-parser: 3.0.6
|
||||
express: 5.2.1
|
||||
express-rate-limit: 7.5.1(express@5.2.1)
|
||||
jose: 6.1.3
|
||||
json-schema-typed: 8.0.2
|
||||
pkce-challenge: 5.0.1
|
||||
raw-body: 3.0.2
|
||||
zod: 4.3.6
|
||||
zod-to-json-schema: 3.25.1(zod@4.3.6)
|
||||
transitivePeerDependencies:
|
||||
- hono
|
||||
- supports-color
|
||||
optional: true
|
||||
|
||||
'@mozilla/readability@0.6.0': {}
|
||||
|
||||
'@mswjs/interceptors@0.40.0':
|
||||
|
|
@ -14793,7 +14722,7 @@ snapshots:
|
|||
react: 19.1.0
|
||||
react-native: 0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0)
|
||||
|
||||
expo-router@6.0.23(52130e04a12e6efd1d6e48b3f2ad01c3):
|
||||
expo-router@6.0.23(w7u2e3gmpia3npio76ytuzityu):
|
||||
dependencies:
|
||||
'@expo/metro-runtime': 6.1.2(expo@54.0.33)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)
|
||||
'@expo/schema-utils': 0.1.8
|
||||
|
|
@ -17180,10 +17109,10 @@ snapshots:
|
|||
is-docker: 2.2.1
|
||||
is-wsl: 2.2.0
|
||||
|
||||
openai@6.10.0(ws@8.18.3)(zod@4.3.6):
|
||||
openai@6.10.0(ws@8.18.3)(zod@3.25.76):
|
||||
optionalDependencies:
|
||||
ws: 8.18.3
|
||||
zod: 4.3.6
|
||||
zod: 3.25.76
|
||||
|
||||
optionator@0.9.4:
|
||||
dependencies:
|
||||
|
|
@ -19471,10 +19400,6 @@ snapshots:
|
|||
dependencies:
|
||||
zod: 3.25.76
|
||||
|
||||
zod-to-json-schema@3.25.1(zod@4.3.6):
|
||||
dependencies:
|
||||
zod: 4.3.6
|
||||
|
||||
zod-validation-error@4.0.2(zod@4.3.6):
|
||||
dependencies:
|
||||
zod: 4.3.6
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue