diff --git a/.changeset/vnext-pluggable-seams.md b/.changeset/vnext-pluggable-seams.md new file mode 100644 index 0000000..0ea0782 --- /dev/null +++ b/.changeset/vnext-pluggable-seams.md @@ -0,0 +1,14 @@ +--- +'@reaatech/agent-mesh': minor +'@reaatech/agent-mesh-classifier': minor +'@reaatech/agent-mesh-session': minor +'@reaatech/agent-mesh-utils': minor +--- + +agent-mesh v-next groundwork — provider-agnostic, backend-agnostic, and host-embeddable, all additive and backward-compatible: + +- **classifier:** pluggable `ClassifierProvider` (+ `createClassifier`) — inject any intent classifier (self-hosted, a different provider, or a host-resolved model) instead of the hard-wired Gemini classifier. Default (no provider) is unchanged: Gemini with a mock fallback. +- **session / utils:** pluggable `SessionStore` and `BreakerStore` with Firestore as the default implementation, dependency-free `InMemory*` adapters, and injection via `setSessionStore` / `setBreakerStore`. The exported service/module functions delegate to the active store; signatures unchanged. (Postgres/Redis adapters to follow.) +- **core:** optional `metadata` passthrough on `IncomingRequest` / `ContextPacket` / `SessionRecord` so an embedding host can carry its own identifiers (e.g. a multi-tenant `orgId`) without the HR-specific `employee_id`; plus the `SessionStore` / `BreakerStore` / `LeaderState` interfaces. + +No breaking changes: the default behaviour (Gemini classifier, Firestore persistence, no metadata) is byte-for-byte unchanged. diff --git a/package.json b/package.json index bdd535c..948dd65 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@vitest/coverage-v8": "^4.1.5", "turbo": "^2.9.16", "typescript": "^6.0.3", + "vite": ">=8.0.16", "vitest": "^4.1.5" }, "pnpm": { @@ -35,9 +36,12 @@ ], "overrides": { "fast-uri": ">=3.1.2", - "hono": ">=4.12.18", + "hono": ">=4.12.25", "ip-address": ">=10.1.1", - "protobufjs": ">=8.0.2" + "protobufjs": ">=8.4.1", + "@grpc/grpc-js": ">=1.14.4", + "ws": ">=8.21.0", + "form-data": ">=4.0.6" } }, "engines": { diff --git a/packages/classifier/src/classifier.provider.ts b/packages/classifier/src/classifier.provider.ts new file mode 100644 index 0000000..d8bae96 --- /dev/null +++ b/packages/classifier/src/classifier.provider.ts @@ -0,0 +1,22 @@ +import type { ClassifierOutput } from '@reaatech/agent-mesh'; +import type { AgentRegistry } from '@reaatech/agent-mesh-registry'; + +/** + * Pluggable intent classifier. + * + * Implement this to supply an alternative to the built-in Gemini classifier — + * e.g. a self-hosted model, a different provider, or a host-resolved model + * (letting the embedding application, rather than agent-mesh, own the inference + * call). Both the built-in `GeminiClassifier` and `MockClassifier` implement it. + * + * Inject via `new ClassifierService(provider)` or {@link createClassifier}. + * `classify` may be synchronous or asynchronous — `ClassifierService` awaits the + * result either way. + */ +export interface ClassifierProvider { + classify( + userInput: string, + registry: AgentRegistry, + priorLanguage?: string, + ): ClassifierOutput | Promise; +} diff --git a/packages/classifier/src/classifier.service.ts b/packages/classifier/src/classifier.service.ts index e6cdbdd..f3d622e 100644 --- a/packages/classifier/src/classifier.service.ts +++ b/packages/classifier/src/classifier.service.ts @@ -2,6 +2,7 @@ import type { ClassifierOutput } from '@reaatech/agent-mesh'; import { env } from '@reaatech/agent-mesh'; import { logger } from '@reaatech/agent-mesh-observability'; import type { AgentRegistry } from '@reaatech/agent-mesh-registry'; +import type { ClassifierProvider } from './classifier.provider.js'; import { detectLanguage } from './localization.js'; import { buildClassifierPrompt, parseClassifierOutput } from './prompt.builder.js'; @@ -35,7 +36,7 @@ function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)); } -class MockClassifier { +class MockClassifier implements ClassifierProvider { classify(userInput: string, registry: AgentRegistry): ClassifierOutput { const input = userInput.toLowerCase(); const detectedLanguage = detectLanguage(userInput); @@ -77,7 +78,7 @@ class MockClassifier { } } -class GeminiClassifier { +class GeminiClassifier implements ClassifierProvider { private model: unknown = null; private initialized = false; @@ -165,20 +166,30 @@ class GeminiClassifier { } export class ClassifierService { - private geminiClassifier: GeminiClassifier | null = null; + private primary: ClassifierProvider | null = null; private mockClassifier: MockClassifier; private useMock = false; - constructor() { + /** + * @param provider Optional classifier used in place of the built-in Gemini + * classifier. When omitted, behaviour is unchanged: Gemini in normal runs + * (with a mock fallback on failure), mock under `NODE_ENV=test`. When + * provided, the injected classifier is the primary and the mock remains the + * on-error fallback — and it is used regardless of `NODE_ENV` (so it is + * testable). + */ + constructor(provider?: ClassifierProvider) { this.mockClassifier = new MockClassifier(); - if (env.NODE_ENV !== 'test') { + if (provider) { + this.primary = provider; + } else if (env.NODE_ENV !== 'test') { const gemini = new GeminiClassifier(); gemini.init().catch(() => { logger.info('Using mock classifier (Gemini unavailable)'); this.useMock = true; }); - this.geminiClassifier = gemini; + this.primary = gemini; } else { this.useMock = true; } @@ -190,11 +201,11 @@ export class ClassifierService { priorLanguage?: string, ): Promise { try { - if (this.useMock || !this.geminiClassifier) { + if (this.useMock || !this.primary) { return this.mockClassifier.classify(userInput, registry); } - return await this.geminiClassifier.classify(userInput, registry, priorLanguage); + return await this.primary.classify(userInput, registry, priorLanguage); } catch (error) { const msg = error instanceof Error ? error.message : 'unknown'; logger.warn('Classifier falling back to mock', { error: msg }); @@ -208,4 +219,12 @@ export class ClassifierService { } } +/** + * Create a {@link ClassifierService}, optionally backed by a custom + * {@link ClassifierProvider}. Equivalent to `new ClassifierService(provider)`. + */ +export function createClassifier(provider?: ClassifierProvider): ClassifierService { + return new ClassifierService(provider); +} + export const classifierService = new ClassifierService(); diff --git a/packages/classifier/src/classifier.test.ts b/packages/classifier/src/classifier.test.ts index 70ca642..7456d29 100644 --- a/packages/classifier/src/classifier.test.ts +++ b/packages/classifier/src/classifier.test.ts @@ -1,5 +1,7 @@ +import type { AgentRegistry } from '@reaatech/agent-mesh-registry'; import { describe, expect, it } from 'vitest'; -import { classifierService, detectLanguage, isRateLimitError } from './index.js'; +import type { ClassifierProvider } from './index.js'; +import { classifierService, createClassifier, detectLanguage, isRateLimitError } from './index.js'; describe('@reaatech/agent-mesh-classifier', () => { it('should export classifier singleton', () => { @@ -14,4 +16,30 @@ describe('@reaatech/agent-mesh-classifier', () => { it('should detect non-rate-limit errors', () => { expect(isRateLimitError(new Error('some error'))).toBe(false); }); + + it('routes through an injected ClassifierProvider', async () => { + const stub: ClassifierProvider = { + classify: () => ({ + agent_id: 'stub-agent', + confidence: 0.99, + ambiguous: false, + detected_language: 'en', + intent_summary: 'from stub', + entities: {}, + }), + }; + const svc = createClassifier(stub); + const out = await svc.classify('anything', [] as unknown as AgentRegistry); + expect(out.agent_id).toBe('stub-agent'); + expect(out.confidence).toBe(0.99); + expect(svc.isMock()).toBe(false); + }); + + it('falls back to the mock (default agent) when no provider is injected', async () => { + const registry = [ + { agent_id: 'default-agent', display_name: 'Default', is_default: true, examples: ['hello'] }, + ] as unknown as AgentRegistry; + const out = await createClassifier().classify('some unmatched text', registry); + expect(out.agent_id).toBe('default-agent'); + }); }); diff --git a/packages/classifier/src/index.ts b/packages/classifier/src/index.ts index 6535df0..dc67b58 100644 --- a/packages/classifier/src/index.ts +++ b/packages/classifier/src/index.ts @@ -1,4 +1,10 @@ -export { ClassifierService, classifierService, isRateLimitError } from './classifier.service.js'; +export type { ClassifierProvider } from './classifier.provider.js'; +export { + ClassifierService, + classifierService, + createClassifier, + isRateLimitError, +} from './classifier.service.js'; export { detectLanguage, FALLBACK_QUESTIONS, diff --git a/packages/core/src/domain.ts b/packages/core/src/domain.ts index 69211c9..4b032be 100644 --- a/packages/core/src/domain.ts +++ b/packages/core/src/domain.ts @@ -9,6 +9,9 @@ export const IncomingRequestSchema = z.object({ locale: z.string().optional(), user_id: z.string().optional(), slack_user_id: z.string().optional(), + // Host-defined passthrough (e.g. multi-tenant `orgId`). agent-mesh does not + // interpret it; it rides through ContextPacket/SessionRecord to the agent. + metadata: z.record(z.string(), z.unknown()).optional(), }); export type IncomingRequest = z.infer; @@ -70,6 +73,7 @@ export const ContextPacketSchema = z.object({ ) .default([]), workflow_state: z.record(z.string(), z.unknown()).default({}), + metadata: z.record(z.string(), z.unknown()).optional(), }); export type ContextPacket = z.infer; @@ -115,6 +119,7 @@ export const SessionRecordSchema = z.object({ workflow_state: z.record(z.string(), z.unknown()).default({}), created_at: z.string().datetime(), updated_at: z.string().datetime(), + metadata: z.record(z.string(), z.unknown()).optional(), ttl: z.date(), }); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index daa3333..d4c14e7 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,3 +1,4 @@ export * from './constants.js'; export * from './domain.js'; export * from './env.js'; +export * from './stores.js'; diff --git a/packages/core/src/stores.ts b/packages/core/src/stores.ts new file mode 100644 index 0000000..2e2839e --- /dev/null +++ b/packages/core/src/stores.ts @@ -0,0 +1,50 @@ +import type { CircuitBreakerState, SessionRecord, SessionStatus, TurnEntry } from './domain.js'; + +/** + * Persistence seam for multi-turn sessions. The built-in Firestore implementation + * is the default; alternative backends (Postgres, Redis, in-memory) implement this + * interface and are injected via the session package's `setSessionStore`. + * + * Policy (TTL, max-turns) is passed in by the caller so each backend owns only the + * atomic read-modify-write, not the business rules. + */ +export interface SessionStore { + create(record: SessionRecord): Promise; + get(sessionId: string): Promise; + /** Active, non-expired session for a user, if any. */ + getActiveByUser(userId: string): Promise; + /** Append a turn atomically, trimming to `maxTurns` and refreshing TTL by `ttlMs`. */ + appendTurn( + sessionId: string, + turn: TurnEntry, + opts: { maxTurns: number; ttlMs: number }, + ): Promise; + updateWorkflowState(sessionId: string, workflowState: Record): Promise; + /** Seed an existing session's history/state (used when resuming a prior session). */ + seed( + sessionId: string, + data: { turn_history: TurnEntry[]; workflow_state: Record }, + ): Promise; + close(sessionId: string, status: Exclude): Promise; +} + +/** Result of a leadership-acquisition attempt (used by circuit-breaker sync). */ +export interface LeaderState { + isLeader: boolean; + leaderId: string; + lastHeartbeat: number; + leaseExpiresAt: number; +} + +/** + * Persistence seam for circuit-breaker state + leader election. The built-in + * Firestore implementation is the default; alternative backends implement this + * interface and are injected via the utils package's `setBreakerStore`. + */ +export interface BreakerStore { + load(agentId: string): Promise; + loadAll(): Promise>; + persist(state: CircuitBreakerState, instanceId: string): Promise; + /** Attempt to acquire/renew the sync leadership lease for `instanceId`. */ + acquireLeadership(instanceId: string, leaseMs: number): Promise; +} diff --git a/packages/session/src/index.ts b/packages/session/src/index.ts index 11e11ae..57da378 100644 --- a/packages/session/src/index.ts +++ b/packages/session/src/index.ts @@ -9,3 +9,11 @@ export { resumeSession, updateWorkflowState, } from './session.service.js'; +export { + FirestoreSessionStore, + getSessionStore, + InMemorySessionStore, + resetSessionStore, + type SessionStore, + setSessionStore, +} from './session.store.js'; diff --git a/packages/session/src/session.service.ts b/packages/session/src/session.service.ts index 671e449..e8d2915 100644 --- a/packages/session/src/session.service.ts +++ b/packages/session/src/session.service.ts @@ -1,12 +1,10 @@ import crypto from 'node:crypto'; -import { FieldValue, Timestamp } from '@google-cloud/firestore'; import { PubSub } from '@google-cloud/pubsub'; import type { SessionRecord, SessionStatus, TurnEntry } from '@reaatech/agent-mesh'; import { env, PUBSUB_TOPICS } from '@reaatech/agent-mesh'; -import { getFirestore } from './firestoreClient.js'; +import { getSessionStore } from './session.store.js'; const uuidv4 = crypto.randomUUID; -const SESSIONS_COLLECTION = 'sessions'; function getSessionTtlMs(): number { return env.SESSION_TTL_MINUTES * 60 * 1000; @@ -16,28 +14,12 @@ function getSessionMaxTurns(): number { return env.SESSION_MAX_TURNS; } -function mapSessionSnapshot(id: string, data: Record): SessionRecord { - const ttlValue = data.ttl as { toDate?: () => Date } | Date | undefined; - const ttl = - ttlValue instanceof Date - ? ttlValue - : (ttlValue?.toDate?.() ?? new Date(Date.now() + getSessionTtlMs())); - - return { - session_id: id, - user_id: String(data.user_id ?? ''), - employee_id: String(data.employee_id ?? ''), - status: data.status as SessionStatus, - active_agent: String(data.active_agent ?? ''), - turn_history: (data.turn_history as TurnEntry[] | undefined) ?? [], - workflow_state: (data.workflow_state as Record | undefined) ?? {}, - created_at: String(data.created_at ?? new Date().toISOString()), - updated_at: String(data.updated_at ?? new Date().toISOString()), - ttl, - }; -} - async function publishSessionEvent(payload: Record): Promise { + // Best-effort event publish; skipped under test (no GCP credentials), mirroring + // the classifier's Gemini guard. Prod behaviour is unchanged. + if (env.NODE_ENV === 'test') { + return; + } try { const pubsub = new PubSub({ projectId: env.GOOGLE_CLOUD_PROJECT }); await pubsub.topic(PUBSUB_TOPICS.SESSION_EVENTS).publishMessage({ json: payload }); @@ -51,11 +33,9 @@ export async function createSession(data: { employeeId: string; activeAgent: string; }): Promise { - const firestore = getFirestore(); const now = new Date(); - const ttl = new Date(now.getTime() + getSessionTtlMs()); - - const session: Omit = { + const record: SessionRecord = { + session_id: uuidv4(), user_id: data.userId, employee_id: data.employeeId, status: 'active', @@ -64,84 +44,25 @@ export async function createSession(data: { workflow_state: {}, created_at: now.toISOString(), updated_at: now.toISOString(), - ttl, + ttl: new Date(now.getTime() + getSessionTtlMs()), }; - const sessionId = uuidv4(); - await firestore - .collection(SESSIONS_COLLECTION) - .doc(sessionId) - .create({ - ...session, - ttl: Timestamp.fromDate(ttl), - }); - - return { - session_id: sessionId, - ...session, - }; + await getSessionStore().create(record); + return record; } export async function getActiveSession(userId: string): Promise { - const firestore = getFirestore(); - const now = new Date(); - - const snapshot = await firestore - .collection(SESSIONS_COLLECTION) - .where('user_id', '==', userId) - .where('status', '==', 'active') - .where('ttl', '>', Timestamp.fromDate(now)) - .limit(1) - .get(); - - if (snapshot.empty) { - return null; - } - - const docSnapshot = snapshot.docs[0]; - if (!docSnapshot) { - return null; - } - - return mapSessionSnapshot(docSnapshot.id, docSnapshot.data() as Record); + return getSessionStore().getActiveByUser(userId); } export async function getSessionById(sessionId: string): Promise { - const firestore = getFirestore(); - const snapshot = await firestore.collection(SESSIONS_COLLECTION).doc(sessionId).get(); - - if (!snapshot.exists) { - return null; - } - - const data = snapshot.data(); - if (!data) { - return null; - } - - return mapSessionSnapshot(snapshot.id, data as Record); + return getSessionStore().get(sessionId); } export async function appendTurn(sessionId: string, turn: TurnEntry): Promise { - const firestore = getFirestore(); - const docRef = firestore.collection(SESSIONS_COLLECTION).doc(sessionId); - - await firestore.runTransaction(async (transaction) => { - const doc = await transaction.get(docRef); - if (!doc.exists) { - throw new Error(`Session ${sessionId} not found`); - } - - const currentHistory = ((doc.data()?.turn_history as TurnEntry[] | undefined) ?? []).slice( - -getSessionMaxTurns() + 1, - ); - const refreshedTtl = new Date(Date.now() + getSessionTtlMs()); - - transaction.update(docRef, { - turn_history: [...currentHistory, turn], - updated_at: new Date().toISOString(), - ttl: Timestamp.fromDate(refreshedTtl), - }); + return getSessionStore().appendTurn(sessionId, turn, { + maxTurns: getSessionMaxTurns(), + ttlMs: getSessionTtlMs(), }); } @@ -149,24 +70,14 @@ export async function updateWorkflowState( sessionId: string, workflowState: Record, ): Promise { - const firestore = getFirestore(); - await firestore.collection(SESSIONS_COLLECTION).doc(sessionId).update({ - workflow_state: workflowState, - updated_at: new Date().toISOString(), - }); + return getSessionStore().updateWorkflowState(sessionId, workflowState); } export async function closeSession( sessionId: string, status: Exclude, ): Promise { - const firestore = getFirestore(); - - await firestore.collection(SESSIONS_COLLECTION).doc(sessionId).update({ - status, - updated_at: new Date().toISOString(), - ttl: FieldValue.delete(), - }); + await getSessionStore().close(sessionId, status); await publishSessionEvent({ session_id: sessionId, @@ -188,15 +99,10 @@ export async function resumeSession(priorSessionId: string): Promise): SessionRecord { + const ttlValue = data.ttl as { toDate?: () => Date } | Date | undefined; + const ttl = + ttlValue instanceof Date + ? ttlValue + : (ttlValue?.toDate?.() ?? new Date(Date.now() + env.SESSION_TTL_MINUTES * 60 * 1000)); + + return { + session_id: id, + user_id: String(data.user_id ?? ''), + employee_id: String(data.employee_id ?? ''), + status: data.status as SessionStatus, + active_agent: String(data.active_agent ?? ''), + turn_history: (data.turn_history as TurnEntry[] | undefined) ?? [], + workflow_state: (data.workflow_state as Record | undefined) ?? {}, + created_at: String(data.created_at ?? new Date().toISOString()), + updated_at: String(data.updated_at ?? new Date().toISOString()), + metadata: data.metadata as Record | undefined, + ttl, + }; +} + +/** Default persistence backend — behaviour-identical to the pre-extraction service. */ +export class FirestoreSessionStore implements SessionStore { + async create(record: SessionRecord): Promise { + const { session_id, ttl, ...rest } = record; + await getFirestore() + .collection(SESSIONS_COLLECTION) + .doc(session_id) + .create({ ...rest, ttl: Timestamp.fromDate(ttl) }); + } + + async get(sessionId: string): Promise { + const snapshot = await getFirestore().collection(SESSIONS_COLLECTION).doc(sessionId).get(); + if (!snapshot.exists) { + return null; + } + const data = snapshot.data(); + if (!data) { + return null; + } + return mapSessionSnapshot(snapshot.id, data as Record); + } + + async getActiveByUser(userId: string): Promise { + const snapshot = await getFirestore() + .collection(SESSIONS_COLLECTION) + .where('user_id', '==', userId) + .where('status', '==', 'active') + .where('ttl', '>', Timestamp.fromDate(new Date())) + .limit(1) + .get(); + + const docSnapshot = snapshot.docs[0]; + if (snapshot.empty || !docSnapshot) { + return null; + } + return mapSessionSnapshot(docSnapshot.id, docSnapshot.data() as Record); + } + + async appendTurn( + sessionId: string, + turn: TurnEntry, + opts: { maxTurns: number; ttlMs: number }, + ): Promise { + const firestore = getFirestore(); + const docRef = firestore.collection(SESSIONS_COLLECTION).doc(sessionId); + + await firestore.runTransaction(async (transaction) => { + const doc = await transaction.get(docRef); + if (!doc.exists) { + throw new Error(`Session ${sessionId} not found`); + } + + const currentHistory = ((doc.data()?.turn_history as TurnEntry[] | undefined) ?? []).slice( + -opts.maxTurns + 1, + ); + + transaction.update(docRef, { + turn_history: [...currentHistory, turn], + updated_at: new Date().toISOString(), + ttl: Timestamp.fromDate(new Date(Date.now() + opts.ttlMs)), + }); + }); + } + + async updateWorkflowState( + sessionId: string, + workflowState: Record, + ): Promise { + await getFirestore().collection(SESSIONS_COLLECTION).doc(sessionId).update({ + workflow_state: workflowState, + updated_at: new Date().toISOString(), + }); + } + + async seed( + sessionId: string, + data: { turn_history: TurnEntry[]; workflow_state: Record }, + ): Promise { + await getFirestore().collection(SESSIONS_COLLECTION).doc(sessionId).update({ + turn_history: data.turn_history, + workflow_state: data.workflow_state, + updated_at: new Date().toISOString(), + }); + } + + async close(sessionId: string, status: Exclude): Promise { + await getFirestore().collection(SESSIONS_COLLECTION).doc(sessionId).update({ + status, + updated_at: new Date().toISOString(), + ttl: FieldValue.delete(), + }); + } +} + +/** Dependency-free store for tests, local dev, and single-instance embedding. */ +export class InMemorySessionStore implements SessionStore { + private readonly records = new Map(); + + async create(record: SessionRecord): Promise { + this.records.set(record.session_id, { ...record }); + } + + async get(sessionId: string): Promise { + const record = this.records.get(sessionId); + return record ? { ...record } : null; + } + + async getActiveByUser(userId: string): Promise { + const now = Date.now(); + for (const record of this.records.values()) { + if (record.user_id === userId && record.status === 'active' && record.ttl.getTime() > now) { + return { ...record }; + } + } + return null; + } + + async appendTurn( + sessionId: string, + turn: TurnEntry, + opts: { maxTurns: number; ttlMs: number }, + ): Promise { + const record = this.records.get(sessionId); + if (!record) { + throw new Error(`Session ${sessionId} not found`); + } + const currentHistory = record.turn_history.slice(-opts.maxTurns + 1); + record.turn_history = [...currentHistory, turn]; + record.updated_at = new Date().toISOString(); + record.ttl = new Date(Date.now() + opts.ttlMs); + } + + async updateWorkflowState( + sessionId: string, + workflowState: Record, + ): Promise { + const record = this.records.get(sessionId); + if (!record) { + return; + } + record.workflow_state = workflowState; + record.updated_at = new Date().toISOString(); + } + + async seed( + sessionId: string, + data: { turn_history: TurnEntry[]; workflow_state: Record }, + ): Promise { + const record = this.records.get(sessionId); + if (!record) { + return; + } + record.turn_history = data.turn_history; + record.workflow_state = data.workflow_state; + record.updated_at = new Date().toISOString(); + } + + async close(sessionId: string, status: Exclude): Promise { + const record = this.records.get(sessionId); + if (!record) { + return; + } + record.status = status; + record.updated_at = new Date().toISOString(); + } +} + +let activeStore: SessionStore | null = null; + +/** Inject a custom {@link SessionStore}. Overrides the default Firestore backend. */ +export function setSessionStore(store: SessionStore): void { + activeStore = store; +} + +/** The active store — the injected one, or a lazily-created Firestore store by default. */ +export function getSessionStore(): SessionStore { + if (!activeStore) { + activeStore = new FirestoreSessionStore(); + } + return activeStore; +} + +/** Reset to the default (Firestore) store. Primarily for tests. */ +export function resetSessionStore(): void { + activeStore = null; +} diff --git a/packages/session/src/session.test.ts b/packages/session/src/session.test.ts index 25cc455..2b57573 100644 --- a/packages/session/src/session.test.ts +++ b/packages/session/src/session.test.ts @@ -1,5 +1,14 @@ -import { describe, expect, it } from 'vitest'; +import { afterEach, describe, expect, it } from 'vitest'; import { getFirestore, resetFirestore } from './index.js'; +import { + appendTurn, + closeSession, + createSession, + getActiveSession, + getSessionById, + resumeSession, +} from './session.service.js'; +import { InMemorySessionStore, resetSessionStore, setSessionStore } from './session.store.js'; describe('@reaatech/agent-mesh-session', () => { it('should export firestore client factory', () => { @@ -10,3 +19,39 @@ describe('@reaatech/agent-mesh-session', () => { expect(typeof resetFirestore).toBe('function'); }); }); + +describe('SessionStore (injected InMemory backend)', () => { + afterEach(() => resetSessionStore()); + + it('drives the full session lifecycle through the service functions', async () => { + setSessionStore(new InMemorySessionStore()); + + const created = await createSession({ + userId: 'user-1', + employeeId: 'emp-1', + activeAgent: 'agent-a', + }); + expect(created.status).toBe('active'); + + const active = await getActiveSession('user-1'); + expect(active?.session_id).toBe(created.session_id); + + await appendTurn(created.session_id, { + role: 'user', + content: 'hello', + timestamp: new Date().toISOString(), + }); + const withTurn = await getSessionById(created.session_id); + expect(withTurn?.turn_history).toHaveLength(1); + + const resumed = await resumeSession(created.session_id); + expect(resumed?.session_id).not.toBe(created.session_id); + expect(resumed?.turn_history).toHaveLength(1); + + // prior session is closed → no longer the active one + const priorClosed = await getSessionById(created.session_id); + expect(priorClosed?.status).toBe('completed'); + await closeSession(resumed?.session_id ?? '', 'completed'); + expect(await getActiveSession('user-1')).toBeNull(); + }); +}); diff --git a/packages/utils/src/breaker.store.ts b/packages/utils/src/breaker.store.ts new file mode 100644 index 0000000..bab8606 --- /dev/null +++ b/packages/utils/src/breaker.store.ts @@ -0,0 +1,178 @@ +import type { + BreakerStore, + CircuitBreakerState, + CircuitState, + LeaderState, +} from '@reaatech/agent-mesh'; +import { getFirestore } from '@reaatech/agent-mesh-session'; + +export type { BreakerStore } from '@reaatech/agent-mesh'; + +const CIRCUIT_BREAKERS_COLLECTION = 'circuit_breakers'; +const LEADER_ELECTION_COLLECTION = 'leader_election'; +const LEADER_DOC_ID = 'circuit_breaker_sync_leader'; + +function mapBreakerData(data: Record): CircuitBreakerState { + return { + agent_id: data.agent_id as string, + state: data.state as CircuitState, + failure_count: data.failure_count as number, + success_count: data.success_count as number, + last_failure_time: data.last_failure_time as number | undefined, + last_state_change: data.last_state_change as number, + half_open_calls: data.half_open_calls as number, + backoff_multiplier: data.backoff_multiplier as number, + }; +} + +/** Default persistence backend — behaviour-identical to the pre-extraction module. */ +export class FirestoreBreakerStore implements BreakerStore { + async load(agentId: string): Promise { + try { + const doc = await getFirestore().collection(CIRCUIT_BREAKERS_COLLECTION).doc(agentId).get(); + const data = doc.exists ? doc.data() : null; + return data ? mapBreakerData(data) : null; + } catch { + return null; + } + } + + async loadAll(): Promise> { + const snapshot = await getFirestore().collection(CIRCUIT_BREAKERS_COLLECTION).get(); + const states = new Map(); + for (const doc of snapshot.docs) { + const data = doc.data(); + if (!data) { + continue; + } + const state = mapBreakerData(data); + states.set(state.agent_id, state); + } + return states; + } + + async persist(state: CircuitBreakerState, instanceId: string): Promise { + const docRef = getFirestore().collection(CIRCUIT_BREAKERS_COLLECTION).doc(state.agent_id); + + for (let attempt = 0; attempt < 3; attempt++) { + try { + await docRef.set( + { ...state, last_synced: Date.now(), synced_by: instanceId }, + { merge: true }, + ); + return; + } catch (error) { + const message = error instanceof Error ? error.message.toLowerCase() : ''; + const retryable = + message.includes('quota') || + message.includes('deadline') || + message.includes('unavailable'); + + if (!retryable || attempt === 2) { + return; + } + await new Promise((resolve) => setTimeout(resolve, 250 * 2 ** attempt)); + } + } + } + + async acquireLeadership(instanceId: string, leaseMs: number): Promise { + const firestore = getFirestore(); + const now = Date.now(); + const leaseExpiresAt = now + leaseMs; + const leaderRef = firestore.collection(LEADER_ELECTION_COLLECTION).doc(LEADER_DOC_ID); + + let result: LeaderState | null = null; + + await firestore.runTransaction(async (transaction) => { + const doc = await transaction.get(leaderRef); + const data = doc.exists ? doc.data() : null; + + if (!data) { + transaction.set(leaderRef, { + leader_id: instanceId, + last_heartbeat: now, + lease_expires_at: leaseExpiresAt, + acquired_at: now, + }); + result = { isLeader: true, leaderId: instanceId, lastHeartbeat: now, leaseExpiresAt }; + return; + } + + const currentLeaseExpiresAt = data.lease_expires_at as number; + const currentLeaderId = data.leader_id as string; + + if (now > currentLeaseExpiresAt || currentLeaderId === instanceId) { + transaction.update(leaderRef, { + leader_id: instanceId, + last_heartbeat: now, + lease_expires_at: leaseExpiresAt, + }); + result = { isLeader: true, leaderId: instanceId, lastHeartbeat: now, leaseExpiresAt }; + } else { + result = { + isLeader: false, + leaderId: currentLeaderId, + lastHeartbeat: data.last_heartbeat as number, + leaseExpiresAt: currentLeaseExpiresAt, + }; + } + }); + + if (!result) { + throw new Error('Leadership acquisition produced no result'); + } + return result; + } +} + +/** + * Dependency-free store for tests, local dev, and single-instance embedding. + * A single instance is always the leader (no cross-instance coordination). + */ +export class InMemoryBreakerStore implements BreakerStore { + private readonly states = new Map(); + + async load(agentId: string): Promise { + const state = this.states.get(agentId); + return state ? { ...state } : null; + } + + async loadAll(): Promise> { + return new Map(Array.from(this.states, ([id, state]) => [id, { ...state }])); + } + + async persist(state: CircuitBreakerState, _instanceId: string): Promise { + this.states.set(state.agent_id, { ...state }); + } + + async acquireLeadership(instanceId: string, leaseMs: number): Promise { + const now = Date.now(); + return { + isLeader: true, + leaderId: instanceId, + lastHeartbeat: now, + leaseExpiresAt: now + leaseMs, + }; + } +} + +let activeStore: BreakerStore | null = null; + +/** Inject a custom {@link BreakerStore}. Overrides the default Firestore backend. */ +export function setBreakerStore(store: BreakerStore): void { + activeStore = store; +} + +/** The active store — the injected one, or a lazily-created Firestore store by default. */ +export function getBreakerStore(): BreakerStore { + if (!activeStore) { + activeStore = new FirestoreBreakerStore(); + } + return activeStore; +} + +/** Reset to the default (Firestore) store. Primarily for tests. */ +export function resetBreakerStore(): void { + activeStore = null; +} diff --git a/packages/utils/src/circuitBreaker.persistence.ts b/packages/utils/src/circuitBreaker.persistence.ts index 71ba30b..73a1ff9 100644 --- a/packages/utils/src/circuitBreaker.persistence.ts +++ b/packages/utils/src/circuitBreaker.persistence.ts @@ -1,21 +1,11 @@ -import type { CircuitBreakerState, CircuitState } from '@reaatech/agent-mesh'; +import type { CircuitBreakerState, LeaderState } from '@reaatech/agent-mesh'; import { env } from '@reaatech/agent-mesh'; -import { getFirestore } from '@reaatech/agent-mesh-session'; +import { getBreakerStore } from './breaker.store.js'; import { circuitBreaker } from './circuitBreaker.js'; -const CIRCUIT_BREAKERS_COLLECTION = 'circuit_breakers'; -const LEADER_ELECTION_COLLECTION = 'leader_election'; -const LEADER_DOC_ID = 'circuit_breaker_sync_leader'; const LEADER_LEASE_MS = env.CB_LEADER_LEASE_MS; const SYNC_INTERVAL_MS = env.CB_SYNC_INTERVAL_MS; -interface LeaderState { - isLeader: boolean; - leaderId: string; - lastHeartbeat: number; - leaseExpiresAt: number; -} - let currentLeaderState: LeaderState | null = null; let syncIntervalHandle: ReturnType | null = null; @@ -32,77 +22,13 @@ function getOrCreateInstanceId(): string { } async function tryAcquireLeadership(): Promise { - const firestore = getFirestore(); - const instanceId = getOrCreateInstanceId(); - const now = Date.now(); - const leaseExpiresAt = now + LEADER_LEASE_MS; - - const leaderRef = firestore.collection(LEADER_ELECTION_COLLECTION).doc(LEADER_DOC_ID); - try { - await firestore.runTransaction(async (transaction) => { - const doc = await transaction.get(leaderRef); - - if (!doc.exists) { - transaction.set(leaderRef, { - leader_id: instanceId, - last_heartbeat: now, - lease_expires_at: leaseExpiresAt, - acquired_at: now, - }); - currentLeaderState = { - isLeader: true, - leaderId: instanceId, - lastHeartbeat: now, - leaseExpiresAt, - }; - return; - } - - const data = doc.data(); - if (!data) { - transaction.set(leaderRef, { - leader_id: instanceId, - last_heartbeat: now, - lease_expires_at: leaseExpiresAt, - acquired_at: now, - }); - currentLeaderState = { - isLeader: true, - leaderId: instanceId, - lastHeartbeat: now, - leaseExpiresAt, - }; - return; - } - - const currentLeaseExpiresAt = data.lease_expires_at as number; - const currentLeaderId = data.leader_id as string; - - if (now > currentLeaseExpiresAt || currentLeaderId === instanceId) { - transaction.update(leaderRef, { - leader_id: instanceId, - last_heartbeat: now, - lease_expires_at: leaseExpiresAt, - }); - currentLeaderState = { - isLeader: true, - leaderId: instanceId, - lastHeartbeat: now, - leaseExpiresAt, - }; - } else { - currentLeaderState = { - isLeader: false, - leaderId: currentLeaderId, - lastHeartbeat: data.last_heartbeat as number, - leaseExpiresAt: currentLeaseExpiresAt, - }; - } - }); - - return currentLeaderState?.isLeader ?? false; - } catch (_error) { + currentLeaderState = await getBreakerStore().acquireLeadership( + getOrCreateInstanceId(), + LEADER_LEASE_MS, + ); + return currentLeaderState.isLeader; + } catch { return false; } } @@ -119,96 +45,17 @@ export function getLeaderId(): string | null { } export async function persistCircuitBreakerState(state: CircuitBreakerState): Promise { - const firestore = getFirestore(); - const docRef = firestore.collection(CIRCUIT_BREAKERS_COLLECTION).doc(state.agent_id); - - for (let attempt = 0; attempt < 3; attempt++) { - try { - await docRef.set( - { - ...state, - last_synced: Date.now(), - synced_by: getOrCreateInstanceId(), - }, - { merge: true }, - ); - return; - } catch (_error) { - const message = _error instanceof Error ? _error.message.toLowerCase() : ''; - const retryable = - message.includes('quota') || - message.includes('deadline') || - message.includes('unavailable'); - - if (!retryable || attempt === 2) { - return; - } - - const backoffMs = 250 * 2 ** attempt; - await new Promise((resolve) => setTimeout(resolve, backoffMs)); - } - } + await getBreakerStore().persist(state, getOrCreateInstanceId()); } export async function loadCircuitBreakerState( agentId: string, ): Promise { - const firestore = getFirestore(); - const docRef = firestore.collection(CIRCUIT_BREAKERS_COLLECTION).doc(agentId); - - try { - const doc = await docRef.get(); - if (!doc.exists) { - return null; - } - - const data = doc.data(); - if (!data) { - return null; - } - - return { - agent_id: data.agent_id as string, - state: data.state as CircuitState, - failure_count: data.failure_count as number, - success_count: data.success_count as number, - last_failure_time: data.last_failure_time as number | undefined, - last_state_change: data.last_state_change as number, - half_open_calls: data.half_open_calls as number, - backoff_multiplier: data.backoff_multiplier as number, - }; - } catch { - return null; - } + return getBreakerStore().load(agentId); } export async function loadAllCircuitBreakerStates(): Promise> { - const firestore = getFirestore(); - const snapshot = await firestore.collection(CIRCUIT_BREAKERS_COLLECTION).get(); - - const states = new Map(); - - for (const doc of snapshot.docs) { - const data = doc.data(); - if (!data) { - continue; - } - - const state: CircuitBreakerState = { - agent_id: data.agent_id as string, - state: data.state as CircuitState, - failure_count: data.failure_count as number, - success_count: data.success_count as number, - last_failure_time: data.last_failure_time as number | undefined, - last_state_change: data.last_state_change as number, - half_open_calls: data.half_open_calls as number, - backoff_multiplier: data.backoff_multiplier as number, - }; - - states.set(state.agent_id, state); - } - - return states; + return getBreakerStore().loadAll(); } async function syncStates(): Promise { diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index e9af5e0..534e58a 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,3 +1,11 @@ +export { + type BreakerStore, + FirestoreBreakerStore, + getBreakerStore, + InMemoryBreakerStore, + resetBreakerStore, + setBreakerStore, +} from './breaker.store.js'; export { circuitBreaker } from './circuitBreaker.js'; export { clearLocalState, diff --git a/packages/utils/src/utils.test.ts b/packages/utils/src/utils.test.ts index 0ee9fba..a4f7582 100644 --- a/packages/utils/src/utils.test.ts +++ b/packages/utils/src/utils.test.ts @@ -1,5 +1,7 @@ -import { describe, expect, it } from 'vitest'; -import { circuitBreaker } from './index.js'; +import type { CircuitBreakerState } from '@reaatech/agent-mesh'; +import { afterEach, describe, expect, it } from 'vitest'; +import { InMemoryBreakerStore, resetBreakerStore, setBreakerStore } from './breaker.store.js'; +import { circuitBreaker, loadCircuitBreakerState, persistCircuitBreakerState } from './index.js'; describe('@reaatech/agent-mesh-utils', () => { it('should export circuitBreaker singleton', () => { @@ -16,3 +18,27 @@ describe('@reaatech/agent-mesh-utils', () => { expect(circuitBreaker.canCall('test-agent-2')).toBe(true); }); }); + +describe('BreakerStore (injected InMemory backend)', () => { + afterEach(() => resetBreakerStore()); + + it('persists and loads breaker state through the module functions', async () => { + setBreakerStore(new InMemoryBreakerStore()); + + const state: CircuitBreakerState = { + agent_id: 'agent-x', + state: 'OPEN', + failure_count: 5, + success_count: 0, + last_state_change: Date.now(), + half_open_calls: 0, + backoff_multiplier: 2, + }; + + expect(await loadCircuitBreakerState('agent-x')).toBeNull(); + await persistCircuitBreakerState(state); + const loaded = await loadCircuitBreakerState('agent-x'); + expect(loaded?.state).toBe('OPEN'); + expect(loaded?.failure_count).toBe(5); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b98f255..e430e73 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6,9 +6,12 @@ settings: overrides: fast-uri: '>=3.1.2' - hono: '>=4.12.18' + hono: '>=4.12.25' ip-address: '>=10.1.1' - protobufjs: '>=8.0.2' + protobufjs: '>=8.4.1' + '@grpc/grpc-js': '>=1.14.4' + ws: '>=8.21.0' + form-data: '>=4.0.6' importers: @@ -35,9 +38,12 @@ importers: typescript: specifier: ^6.0.3 version: 6.0.3 + vite: + specifier: '>=8.0.16' + version: 8.1.3(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0) vitest: specifier: ^4.1.5 - version: 4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0)) + version: 4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.1.3(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0)) e2e: dependencies: @@ -77,7 +83,7 @@ importers: version: 7.2.2 vitest: specifier: ^4.1.5 - version: 4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0)) + version: 4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.1.3(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0)) examples/orchestrator: dependencies: @@ -148,7 +154,7 @@ importers: version: 25.8.0 tsup: specifier: ^8.4.0 - version: 8.5.1(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0) + version: 8.5.1(postcss@8.5.16)(typescript@6.0.3)(yaml@2.9.0) packages/confidence: dependencies: @@ -170,7 +176,7 @@ importers: version: 25.8.0 tsup: specifier: ^8.4.0 - version: 8.5.1(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0) + version: 8.5.1(postcss@8.5.16)(typescript@6.0.3)(yaml@2.9.0) packages/core: dependencies: @@ -183,7 +189,7 @@ importers: version: 25.8.0 tsup: specifier: ^8.4.0 - version: 8.5.1(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0) + version: 8.5.1(postcss@8.5.16)(typescript@6.0.3)(yaml@2.9.0) packages/gateway: dependencies: @@ -229,7 +235,7 @@ importers: version: 25.8.0 tsup: specifier: ^8.4.0 - version: 8.5.1(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0) + version: 8.5.1(postcss@8.5.16)(typescript@6.0.3)(yaml@2.9.0) packages/mcp-server: dependencies: @@ -263,7 +269,7 @@ importers: version: 25.8.0 tsup: specifier: ^8.4.0 - version: 8.5.1(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0) + version: 8.5.1(postcss@8.5.16)(typescript@6.0.3)(yaml@2.9.0) packages/observability: dependencies: @@ -309,7 +315,7 @@ importers: version: 25.8.0 tsup: specifier: ^8.4.0 - version: 8.5.1(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0) + version: 8.5.1(postcss@8.5.16)(typescript@6.0.3)(yaml@2.9.0) packages/registry: dependencies: @@ -334,7 +340,7 @@ importers: version: 25.8.0 tsup: specifier: ^8.4.0 - version: 8.5.1(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0) + version: 8.5.1(postcss@8.5.16)(typescript@6.0.3)(yaml@2.9.0) packages/router: dependencies: @@ -362,7 +368,7 @@ importers: version: 25.8.0 tsup: specifier: ^8.4.0 - version: 8.5.1(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0) + version: 8.5.1(postcss@8.5.16)(typescript@6.0.3)(yaml@2.9.0) packages/session: dependencies: @@ -390,7 +396,7 @@ importers: version: 25.8.0 tsup: specifier: ^8.4.0 - version: 8.5.1(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0) + version: 8.5.1(postcss@8.5.16)(typescript@6.0.3)(yaml@2.9.0) packages/utils: dependencies: @@ -412,7 +418,7 @@ importers: version: 25.8.0 tsup: specifier: ^8.4.0 - version: 8.5.1(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0) + version: 8.5.1(postcss@8.5.16)(typescript@6.0.3)(yaml@2.9.0) packages: @@ -562,14 +568,14 @@ packages: '@dabh/diagnostics@2.0.8': resolution: {integrity: sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q==} - '@emnapi/core@1.10.0': - resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + '@emnapi/core@1.11.1': + resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} - '@emnapi/runtime@1.10.0': - resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@emnapi/runtime@1.11.1': + resolution: {integrity: sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==} - '@emnapi/wasi-threads@1.2.1': - resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + '@emnapi/wasi-threads@1.2.2': + resolution: {integrity: sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==} '@esbuild/aix-ppc64@0.27.7': resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} @@ -772,10 +778,6 @@ packages: '@modelcontextprotocol/sdk': optional: true - '@grpc/grpc-js@1.14.3': - resolution: {integrity: sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==} - engines: {node: '>=12.10.0'} - '@grpc/grpc-js@1.14.4': resolution: {integrity: sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ==} engines: {node: '>=12.10.0'} @@ -789,7 +791,7 @@ packages: resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} engines: {node: '>=18.14.1'} peerDependencies: - hono: '>=4.12.18' + hono: '>=4.12.25' '@inquirer/external-editor@1.0.3': resolution: {integrity: sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==} @@ -836,8 +838,8 @@ packages: '@cfworker/json-schema': optional: true - '@napi-rs/wasm-runtime@1.1.4': - resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 @@ -1062,8 +1064,8 @@ packages: resolution: {integrity: sha512-/UhIkaZgPutTFmQ7RnIJGgDXZmtEJ7Dvi86xNTFWcnRxVRNk/aotsqDJYeEvDP+FSMB2SdW+pQzNMcWP0rwuNA==} engines: {node: '>=14'} - '@oxc-project/types@0.130.0': - resolution: {integrity: sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==} + '@oxc-project/types@0.138.0': + resolution: {integrity: sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==} '@paralleldrive/cuid2@2.3.1': resolution: {integrity: sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==} @@ -1072,91 +1074,91 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@rolldown/binding-android-arm64@1.0.1': - resolution: {integrity: sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==} + '@rolldown/binding-android-arm64@1.1.4': + resolution: {integrity: sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.1': - resolution: {integrity: sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==} + '@rolldown/binding-darwin-arm64@1.1.4': + resolution: {integrity: sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.1': - resolution: {integrity: sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==} + '@rolldown/binding-darwin-x64@1.1.4': + resolution: {integrity: sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.1': - resolution: {integrity: sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==} + '@rolldown/binding-freebsd-x64@1.1.4': + resolution: {integrity: sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.1': - resolution: {integrity: sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==} + '@rolldown/binding-linux-arm-gnueabihf@1.1.4': + resolution: {integrity: sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.1': - resolution: {integrity: sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==} + '@rolldown/binding-linux-arm64-gnu@1.1.4': + resolution: {integrity: sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-arm64-musl@1.0.1': - resolution: {integrity: sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==} + '@rolldown/binding-linux-arm64-musl@1.1.4': + resolution: {integrity: sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@rolldown/binding-linux-ppc64-gnu@1.0.1': - resolution: {integrity: sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==} + '@rolldown/binding-linux-ppc64-gnu@1.1.4': + resolution: {integrity: sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - '@rolldown/binding-linux-s390x-gnu@1.0.1': - resolution: {integrity: sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==} + '@rolldown/binding-linux-s390x-gnu@1.1.4': + resolution: {integrity: sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - '@rolldown/binding-linux-x64-gnu@1.0.1': - resolution: {integrity: sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==} + '@rolldown/binding-linux-x64-gnu@1.1.4': + resolution: {integrity: sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-linux-x64-musl@1.0.1': - resolution: {integrity: sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==} + '@rolldown/binding-linux-x64-musl@1.1.4': + resolution: {integrity: sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@rolldown/binding-openharmony-arm64@1.0.1': - resolution: {integrity: sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==} + '@rolldown/binding-openharmony-arm64@1.1.4': + resolution: {integrity: sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.1': - resolution: {integrity: sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==} + '@rolldown/binding-wasm32-wasi@1.1.4': + resolution: {integrity: sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.1': - resolution: {integrity: sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==} + '@rolldown/binding-win32-arm64-msvc@1.1.4': + resolution: {integrity: sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.1': - resolution: {integrity: sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==} + '@rolldown/binding-win32-x64-msvc@1.1.4': + resolution: {integrity: sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1325,8 +1327,8 @@ packages: cpu: [arm64] os: [win32] - '@tybys/wasm-util@0.10.2': - resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} @@ -1865,8 +1867,8 @@ packages: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} - form-data@4.0.5: - resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} engines: {node: '>= 6'} formdata-polyfill@4.0.10: @@ -2031,12 +2033,16 @@ packages: resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} engines: {node: '>= 0.4'} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + heap-js@2.7.1: resolution: {integrity: sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA==} engines: {node: '>=10.0.0'} - hono@4.12.19: - resolution: {integrity: sha512-xa3eYXYXx68XTT4hZ7dRzsXBhaq85ToSrlUJNoR0gwz/1Ap/CNwX47wfvV7pc/xWhjKVVkLT7zBJy8chhNguqQ==} + hono@4.12.27: + resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==} engines: {node: '>=16.9.0'} html-escaper@2.0.2: @@ -2526,8 +2532,8 @@ packages: yaml: optional: true - postcss@8.5.15: - resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} engines: {node: ^10 || ^12 || >=14} prettier@2.8.8: @@ -2539,10 +2545,6 @@ packages: resolution: {integrity: sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==} engines: {node: '>=18'} - protobufjs@8.3.0: - resolution: {integrity: sha512-JpJpFaR7yKNb6WqKvJJ1MLbiuIQWQnbUUb06nDtf2/i8YWYYLEfP6xf9BwSJoJQg1wAy61EQB8dssQg64oX4aA==} - engines: {node: '>=12.0.0'} - protobufjs@8.5.0: resolution: {integrity: sha512-df1jWDPA5VIBNRtuAHjqr09f2qN5D4Vke1wYqOQg1XJ7ZDpA7BD6L7E4tyChgGRLB5hqk2m79Zsy0WHwV9a84A==} engines: {node: '>=12.0.0'} @@ -2621,8 +2623,8 @@ packages: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true - rolldown@1.0.1: - resolution: {integrity: sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==} + rolldown@1.1.4: + resolution: {integrity: sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -2902,13 +2904,13 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vite@8.0.13: - resolution: {integrity: sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==} + vite@8.1.3: + resolution: {integrity: sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: '@types/node': ^20.19.0 || >=22.12.0 - '@vitejs/devtools': ^0.1.18 + '@vitejs/devtools': ^0.3.0 esbuild: ^0.27.0 || ^0.28.0 jiti: '>=1.21.0' less: ^4.0.0 @@ -3025,8 +3027,8 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.20.1: - resolution: {integrity: sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==} + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 @@ -3282,18 +3284,18 @@ snapshots: enabled: 2.0.0 kuler: 2.0.0 - '@emnapi/core@1.10.0': + '@emnapi/core@1.11.1': dependencies: - '@emnapi/wasi-threads': 1.2.1 + '@emnapi/wasi-threads': 1.2.2 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.10.0': + '@emnapi/runtime@1.11.1': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.1': + '@emnapi/wasi-threads@1.2.2': dependencies: tslib: 2.8.1 optional: true @@ -3388,7 +3390,7 @@ snapshots: fast-deep-equal: 3.1.3 functional-red-black-tree: 1.0.1 google-gax: 5.0.6 - protobufjs: 8.3.0 + protobufjs: 8.6.1 transitivePeerDependencies: - supports-color @@ -3445,8 +3447,8 @@ snapshots: dependencies: google-auth-library: 10.6.2 p-retry: 4.6.2 - protobufjs: 8.3.0 - ws: 8.20.1 + protobufjs: 8.6.1 + ws: 8.21.0 optionalDependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) transitivePeerDependencies: @@ -3454,11 +3456,6 @@ snapshots: - supports-color - utf-8-validate - '@grpc/grpc-js@1.14.3': - dependencies: - '@grpc/proto-loader': 0.8.1 - '@js-sdsl/ordered-map': 4.4.2 - '@grpc/grpc-js@1.14.4': dependencies: '@grpc/proto-loader': 0.8.1 @@ -3471,9 +3468,9 @@ snapshots: protobufjs: 8.5.0 yargs: 17.7.2 - '@hono/node-server@1.19.14(hono@4.12.19)': + '@hono/node-server@1.19.14(hono@4.12.27)': dependencies: - hono: 4.12.19 + hono: 4.12.27 '@inquirer/external-editor@1.0.3(@types/node@25.8.0)': dependencies: @@ -3525,7 +3522,7 @@ snapshots: '@modelcontextprotocol/sdk@1.29.0(zod@4.4.3)': dependencies: - '@hono/node-server': 1.19.14(hono@4.12.19) + '@hono/node-server': 1.19.14(hono@4.12.27) ajv: 8.20.0 ajv-formats: 3.0.1(ajv@8.20.0) content-type: 1.0.5 @@ -3535,7 +3532,7 @@ snapshots: eventsource-parser: 3.0.8 express: 5.2.1 express-rate-limit: 8.5.2(express@5.2.1) - hono: 4.12.19 + hono: 4.12.27 jose: 6.2.3 json-schema-typed: 8.0.2 pkce-challenge: 5.0.1 @@ -3545,11 +3542,11 @@ snapshots: transitivePeerDependencies: - supports-color - '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@tybys/wasm-util': 0.10.2 + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@tybys/wasm-util': 0.10.3 optional: true '@noble/hashes@1.8.0': {} @@ -3598,7 +3595,7 @@ snapshots: '@opentelemetry/exporter-logs-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@grpc/grpc-js': 1.14.3 + '@grpc/grpc-js': 1.14.4 '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) @@ -3628,7 +3625,7 @@ snapshots: '@opentelemetry/exporter-metrics-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@grpc/grpc-js': 1.14.3 + '@grpc/grpc-js': 1.14.4 '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/exporter-metrics-otlp-http': 0.217.0(@opentelemetry/api@1.9.1) @@ -3667,7 +3664,7 @@ snapshots: '@opentelemetry/exporter-trace-otlp-grpc@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@grpc/grpc-js': 1.14.3 + '@grpc/grpc-js': 1.14.4 '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) @@ -3747,7 +3744,7 @@ snapshots: '@opentelemetry/otlp-grpc-exporter-base@0.217.0(@opentelemetry/api@1.9.1)': dependencies: - '@grpc/grpc-js': 1.14.3 + '@grpc/grpc-js': 1.14.4 '@opentelemetry/api': 1.9.1 '@opentelemetry/core': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/otlp-exporter-base': 0.217.0(@opentelemetry/api@1.9.1) @@ -3762,7 +3759,7 @@ snapshots: '@opentelemetry/sdk-logs': 0.217.0(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-metrics': 2.7.1(@opentelemetry/api@1.9.1) '@opentelemetry/sdk-trace-base': 2.7.1(@opentelemetry/api@1.9.1) - protobufjs: 8.3.0 + protobufjs: 8.6.1 '@opentelemetry/propagator-b3@2.7.1(@opentelemetry/api@1.9.1)': dependencies: @@ -3845,7 +3842,7 @@ snapshots: '@opentelemetry/semantic-conventions@1.41.1': {} - '@oxc-project/types@0.130.0': {} + '@oxc-project/types@0.138.0': {} '@paralleldrive/cuid2@2.3.1': dependencies: @@ -3854,53 +3851,53 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@rolldown/binding-android-arm64@1.0.1': + '@rolldown/binding-android-arm64@1.1.4': optional: true - '@rolldown/binding-darwin-arm64@1.0.1': + '@rolldown/binding-darwin-arm64@1.1.4': optional: true - '@rolldown/binding-darwin-x64@1.0.1': + '@rolldown/binding-darwin-x64@1.1.4': optional: true - '@rolldown/binding-freebsd-x64@1.0.1': + '@rolldown/binding-freebsd-x64@1.1.4': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.1': + '@rolldown/binding-linux-arm-gnueabihf@1.1.4': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.1': + '@rolldown/binding-linux-arm64-gnu@1.1.4': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.1': + '@rolldown/binding-linux-arm64-musl@1.1.4': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.1': + '@rolldown/binding-linux-ppc64-gnu@1.1.4': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.1': + '@rolldown/binding-linux-s390x-gnu@1.1.4': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.1': + '@rolldown/binding-linux-x64-gnu@1.1.4': optional: true - '@rolldown/binding-linux-x64-musl@1.0.1': + '@rolldown/binding-linux-x64-musl@1.1.4': optional: true - '@rolldown/binding-openharmony-arm64@1.0.1': + '@rolldown/binding-openharmony-arm64@1.1.4': optional: true - '@rolldown/binding-wasm32-wasi@1.0.1': + '@rolldown/binding-wasm32-wasi@1.1.4': dependencies: - '@emnapi/core': 1.10.0 - '@emnapi/runtime': 1.10.0 - '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + '@emnapi/core': 1.11.1 + '@emnapi/runtime': 1.11.1 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.1': + '@rolldown/binding-win32-arm64-msvc@1.1.4': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.1': + '@rolldown/binding-win32-x64-msvc@1.1.4': optional: true '@rolldown/pluginutils@1.0.1': {} @@ -4005,7 +4002,7 @@ snapshots: '@turbo/windows-arm64@2.9.16': optional: true - '@tybys/wasm-util@0.10.2': + '@tybys/wasm-util@0.10.3': dependencies: tslib: 2.8.1 optional: true @@ -4075,7 +4072,7 @@ snapshots: '@types/cookiejar': 2.1.5 '@types/methods': 1.1.4 '@types/node': 25.8.0 - form-data: 4.0.5 + form-data: 4.0.6 '@types/supertest@7.2.0': dependencies: @@ -4096,7 +4093,7 @@ snapshots: obug: 2.1.1 std-env: 4.1.0 tinyrainbow: 3.1.0 - vitest: 4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0)) + vitest: 4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.1.3(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0)) '@vitest/expect@4.1.6': dependencies: @@ -4107,13 +4104,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.6(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0))': + '@vitest/mocker@4.1.6(vite@8.1.3(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0) + vite: 8.1.3(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0) '@vitest/pretty-format@4.1.6': dependencies: @@ -4414,7 +4411,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.3 + hasown: 2.0.4 esbuild@0.27.7: optionalDependencies: @@ -4569,12 +4566,12 @@ snapshots: cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.5: + form-data@4.0.6: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.3 + hasown: 2.0.4 mime-types: 2.1.35 formdata-polyfill@4.0.10: @@ -4836,9 +4833,13 @@ snapshots: dependencies: function-bind: 1.1.2 + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + heap-js@2.7.1: {} - hono@4.12.19: {} + hono@4.12.27: {} html-escaper@2.0.2: {} @@ -5227,14 +5228,14 @@ snapshots: mlly: 1.8.2 pathe: 2.0.3 - postcss-load-config@6.0.1(postcss@8.5.15)(yaml@2.9.0): + postcss-load-config@6.0.1(postcss@8.5.16)(yaml@2.9.0): dependencies: lilconfig: 3.1.3 optionalDependencies: - postcss: 8.5.15 + postcss: 8.5.16 yaml: 2.9.0 - postcss@8.5.15: + postcss@8.5.16: dependencies: nanoid: 3.3.12 picocolors: 1.1.1 @@ -5246,10 +5247,6 @@ snapshots: dependencies: protobufjs: 8.5.0 - protobufjs@8.3.0: - dependencies: - long: 5.3.2 - protobufjs@8.5.0: dependencies: long: 5.3.2 @@ -5330,26 +5327,26 @@ snapshots: dependencies: glob: 10.5.0 - rolldown@1.0.1: + rolldown@1.1.4: dependencies: - '@oxc-project/types': 0.130.0 + '@oxc-project/types': 0.138.0 '@rolldown/pluginutils': 1.0.1 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.1 - '@rolldown/binding-darwin-arm64': 1.0.1 - '@rolldown/binding-darwin-x64': 1.0.1 - '@rolldown/binding-freebsd-x64': 1.0.1 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.1 - '@rolldown/binding-linux-arm64-gnu': 1.0.1 - '@rolldown/binding-linux-arm64-musl': 1.0.1 - '@rolldown/binding-linux-ppc64-gnu': 1.0.1 - '@rolldown/binding-linux-s390x-gnu': 1.0.1 - '@rolldown/binding-linux-x64-gnu': 1.0.1 - '@rolldown/binding-linux-x64-musl': 1.0.1 - '@rolldown/binding-openharmony-arm64': 1.0.1 - '@rolldown/binding-wasm32-wasi': 1.0.1 - '@rolldown/binding-win32-arm64-msvc': 1.0.1 - '@rolldown/binding-win32-x64-msvc': 1.0.1 + '@rolldown/binding-android-arm64': 1.1.4 + '@rolldown/binding-darwin-arm64': 1.1.4 + '@rolldown/binding-darwin-x64': 1.1.4 + '@rolldown/binding-freebsd-x64': 1.1.4 + '@rolldown/binding-linux-arm-gnueabihf': 1.1.4 + '@rolldown/binding-linux-arm64-gnu': 1.1.4 + '@rolldown/binding-linux-arm64-musl': 1.1.4 + '@rolldown/binding-linux-ppc64-gnu': 1.1.4 + '@rolldown/binding-linux-s390x-gnu': 1.1.4 + '@rolldown/binding-linux-x64-gnu': 1.1.4 + '@rolldown/binding-linux-x64-musl': 1.1.4 + '@rolldown/binding-openharmony-arm64': 1.1.4 + '@rolldown/binding-wasm32-wasi': 1.1.4 + '@rolldown/binding-win32-arm64-msvc': 1.1.4 + '@rolldown/binding-win32-x64-msvc': 1.1.4 rollup@4.60.4: dependencies: @@ -5540,7 +5537,7 @@ snapshots: cookiejar: 2.1.4 debug: 4.4.3 fast-safe-stringify: 2.1.1 - form-data: 4.0.5 + form-data: 4.0.6 formidable: 3.5.4 methods: 1.1.2 mime: 2.6.0 @@ -5625,7 +5622,7 @@ snapshots: tslib@2.8.1: optional: true - tsup@8.5.1(postcss@8.5.15)(typescript@6.0.3)(yaml@2.9.0): + tsup@8.5.1(postcss@8.5.16)(typescript@6.0.3)(yaml@2.9.0): dependencies: bundle-require: 5.1.0(esbuild@0.27.7) cac: 6.7.14 @@ -5636,7 +5633,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.15)(yaml@2.9.0) + postcss-load-config: 6.0.1(postcss@8.5.16)(yaml@2.9.0) resolve-from: 5.0.0 rollup: 4.60.4 source-map: 0.7.6 @@ -5645,7 +5642,7 @@ snapshots: tinyglobby: 0.2.16 tree-kill: 1.2.2 optionalDependencies: - postcss: 8.5.15 + postcss: 8.5.16 typescript: 6.0.3 transitivePeerDependencies: - jiti @@ -5684,12 +5681,12 @@ snapshots: vary@1.1.2: {} - vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0): + vite@8.1.3(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.15 - rolldown: 1.0.1 + postcss: 8.5.16 + rolldown: 1.1.4 tinyglobby: 0.2.17 optionalDependencies: '@types/node': 25.8.0 @@ -5697,10 +5694,10 @@ snapshots: fsevents: 2.3.3 yaml: 2.9.0 - vitest@4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0)): + vitest@4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.8.0)(@vitest/coverage-v8@4.1.6)(vite@8.1.3(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.6 - '@vitest/mocker': 4.1.6(vite@8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0)) + '@vitest/mocker': 4.1.6(vite@8.1.3(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.6 '@vitest/runner': 4.1.6 '@vitest/snapshot': 4.1.6 @@ -5717,7 +5714,7 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.13(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0) + vite: 8.1.3(@types/node@25.8.0)(esbuild@0.27.7)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 @@ -5778,7 +5775,7 @@ snapshots: wrappy@1.0.2: {} - ws@8.20.1: {} + ws@8.21.0: {} y18n@5.0.8: {}