@axiom-lattice/gateway 4.8.4 → 4.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/a2a-BHBLACJV.mjs +17 -0
  2. package/dist/a2a-BHBLACJV.mjs.map +1 -0
  3. package/dist/{chunk-NPHK4SZ6.mjs → chunk-FGSFOXIX.mjs} +2 -2
  4. package/dist/{a2a-FGYX2HH3.mjs → chunk-HYYLYIMH.mjs} +59 -15
  5. package/dist/chunk-HYYLYIMH.mjs.map +1 -0
  6. package/dist/{chunk-73QZQQOT.mjs → chunk-MWWLJMYN.mjs} +3 -2
  7. package/dist/chunk-MWWLJMYN.mjs.map +1 -0
  8. package/dist/embed/{embed-page-AgentDetailSideApp-S5N76UPU.js → embed-page-AgentDetailSideApp-UMITWVYQ.js} +1 -1
  9. package/dist/embed/{embed-page-AgentGovernanceWorkbench-QFMP2M73.js → embed-page-AgentGovernanceWorkbench-UD7RE7PT.js} +1 -1
  10. package/dist/embed/{embed-page-MessageGraph-H3HPZKOH.js → embed-page-MessageGraph-XQWKYDUB.js} +1 -1
  11. package/dist/embed/{embed-page-ReactChart-ZFMANJCT.js → embed-page-ReactChart-LW3GWMY2.js} +1 -1
  12. package/dist/embed/{embed-page-SkillFlow-HY7PD43P.js → embed-page-SkillFlow-X3D346DE.js} +1 -1
  13. package/dist/embed/{embed-page-TasksSideApp-QKEXMHCT.js → embed-page-TasksSideApp-SUMX2GJF.js} +1 -1
  14. package/dist/embed/{embed-page-TeamWorkspace-ZFVAFZXZ.js → embed-page-TeamWorkspace-QMIX2TP2.js} +1 -1
  15. package/dist/embed/{embed-page-TopologyRuntimeView-2BS4OYEZ.js → embed-page-TopologyRuntimeView-ZP6NWNS7.js} +1 -1
  16. package/dist/embed/{embed-page-WorkflowAutomationView-WUI7BSQP.js → embed-page-WorkflowAutomationView-4HD3ISK6.js} +1 -1
  17. package/dist/embed/{embed-page-chunk-4T4QMOUS.js → embed-page-chunk-3VIEL6KL.js} +1 -1
  18. package/dist/embed/{embed-page-chunk-5WYDCL3B.js → embed-page-chunk-MFFCCHGW.js} +158 -158
  19. package/dist/embed/{embed-page-chunk-6HM7ZERK.js → embed-page-chunk-QCKYIPFY.js} +1 -1
  20. package/dist/embed/{embed-page-chunk-XKQ6YPUW.js → embed-page-chunk-QGAGZMX2.js} +1 -1
  21. package/dist/embed/{embed-page-chunk-VJTO6SG6.js → embed-page-chunk-SX7YO5JA.js} +1 -1
  22. package/dist/embed/{embed-page-chunk-7ZBZKXBU.js → embed-page-chunk-T7XXE35M.js} +4 -4
  23. package/dist/embed/{embed-page-chunk-MSHEVUK3.js → embed-page-chunk-YAJUWBF2.js} +1 -1
  24. package/dist/embed/embed-page.js +1 -1
  25. package/dist/index.d.mts +431 -5
  26. package/dist/index.d.ts +431 -5
  27. package/dist/index.js +2939 -1294
  28. package/dist/index.js.map +1 -1
  29. package/dist/index.mjs +1811 -211
  30. package/dist/index.mjs.map +1 -1
  31. package/dist/{open-UVKMONVE.mjs → open-FJ3BY3CL.mjs} +3 -3
  32. package/dist/{open-exposure-VU227IO3.mjs → open-exposure-OX5NKWDG.mjs} +6 -6
  33. package/package.json +7 -7
  34. package/dist/a2a-FGYX2HH3.mjs.map +0 -1
  35. package/dist/chunk-73QZQQOT.mjs.map +0 -1
  36. /package/dist/{chunk-NPHK4SZ6.mjs.map → chunk-FGSFOXIX.mjs.map} +0 -0
  37. /package/dist/{open-UVKMONVE.mjs.map → open-FJ3BY3CL.mjs.map} +0 -0
  38. /package/dist/{open-exposure-VU227IO3.mjs.map → open-exposure-OX5NKWDG.mjs.map} +0 -0
@@ -1,4 +1,4 @@
1
- import{$a as v,Ya as L,_a as I,cb as M,db as b,k as R,m as B,ra as O}from"./embed-page-chunk-5WYDCL3B.js";import{a as P}from"./embed-page-chunk-MB65DZAX.js";import{Fa as C,La as $,Z as N,va as S}from"./embed-page-chunk-FZRTVFHF.js";import{a as V}from"./embed-page-chunk-4BKXE5ON.js";import{e as w}from"./embed-page-chunk-6Y3UYJ6Z.js";var m=w(V());function X(t,o){if(t.parentId&&t.parentId!==t.id&&o.has(t.parentId))return t.parentId;for(let c of t.dependencies??[])if(c!==t.id&&o.has(c))return c}function D(t,o){let u=o?.hideCompleted??!1?t.filter(e=>!b(e.status)):t,g=new Map(u.map(e=>[e.id,e])),d=new Map;for(let e of u)d.set(e.id,X(e,g));let r=new Set;for(let e of d.keys()){if(r.has(e))continue;let l=new Map,p=e,h=0;for(;p!==void 0&&!r.has(p)&&!l.has(p);)l.set(p,h),h+=1,p=d.get(p);if(p!==void 0&&l.has(p)){let y=l.get(p);for(let[i,f]of l)f>=y&&r.add(i)}}for(let e of r)d.set(e,void 0);let x=new Map;for(let e of u)x.set(e.id,[]);for(let e of u){let l=d.get(e.id);l!==void 0&&x.get(l).push(e.id)}let s=e=>({task:g.get(e),children:(x.get(e)??[]).map(s)});return u.filter(e=>d.get(e.id)===void 0).map(e=>s(e.id))}var n=w(P()),{Text:z}=$,Y=16,q=3,J=O(({css:t,token:o})=>({root:t`
1
+ import{$a as v,Ya as L,_a as I,cb as M,db as b,k as R,m as B,ra as O}from"./embed-page-chunk-MFFCCHGW.js";import{a as P}from"./embed-page-chunk-MB65DZAX.js";import{Fa as C,La as $,Z as N,va as S}from"./embed-page-chunk-FZRTVFHF.js";import{a as V}from"./embed-page-chunk-4BKXE5ON.js";import{e as w}from"./embed-page-chunk-6Y3UYJ6Z.js";var m=w(V());function X(t,o){if(t.parentId&&t.parentId!==t.id&&o.has(t.parentId))return t.parentId;for(let c of t.dependencies??[])if(c!==t.id&&o.has(c))return c}function D(t,o){let u=o?.hideCompleted??!1?t.filter(e=>!b(e.status)):t,g=new Map(u.map(e=>[e.id,e])),d=new Map;for(let e of u)d.set(e.id,X(e,g));let r=new Set;for(let e of d.keys()){if(r.has(e))continue;let l=new Map,p=e,h=0;for(;p!==void 0&&!r.has(p)&&!l.has(p);)l.set(p,h),h+=1,p=d.get(p);if(p!==void 0&&l.has(p)){let y=l.get(p);for(let[i,f]of l)f>=y&&r.add(i)}}for(let e of r)d.set(e,void 0);let x=new Map;for(let e of u)x.set(e.id,[]);for(let e of u){let l=d.get(e.id);l!==void 0&&x.get(l).push(e.id)}let s=e=>({task:g.get(e),children:(x.get(e)??[]).map(s)});return u.filter(e=>d.get(e.id)===void 0).map(e=>s(e.id))}var n=w(P()),{Text:z}=$,Y=16,q=3,J=O(({css:t,token:o})=>({root:t`
2
2
  display: flex;
3
3
  flex-direction: column;
4
4
  height: 100%;
@@ -1,4 +1,4 @@
1
- import{b as f}from"./embed-page-chunk-6HM7ZERK.js";import"./embed-page-chunk-5WYDCL3B.js";import"./embed-page-chunk-7ZBZKXBU.js";import{a as b}from"./embed-page-chunk-MB65DZAX.js";import"./embed-page-chunk-VW3NF5NT.js";import"./embed-page-chunk-5JDQ456X.js";import"./embed-page-chunk-6KCBXVIT.js";import{a as g}from"./embed-page-chunk-FZRTVFHF.js";import"./embed-page-chunk-4BKXE5ON.js";import"./embed-page-chunk-IX4WLWCY.js";import"./embed-page-chunk-EUOOO2LS.js";import{b as U,e as s}from"./embed-page-chunk-6Y3UYJ6Z.js";var h=U(d=>{"use strict";var E=g();d.createRoot=E.createRoot,d.hydrateRoot=E.hydrateRoot;var ee});var T=s(h()),k=s(g());var y="axiom-web-app-embed";var x=["axiom:hello","axiom:identity","axiom:send","axiom:config"];function w(e,t){return{source:y,version:1,type:e,payload:t}}var N=6,z=40,B=500;function H(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let t=e,n=Object.keys(t);return n.length!==2||!n.includes("label")||!n.includes("text")?!1:typeof t.label=="string"&&t.label.length>=1&&t.label.length<=z&&typeof t.text=="string"&&t.text.length>=1&&t.text.length<=B}function Q(e){if(!Array.isArray(e))return[];let t=[];for(let n of e){if(t.length>=N)break;H(n)&&t.push({label:n.label,text:n.text})}return t}function _(e){if(typeof e!="object"||e===null)return null;let t=e,n={};if(typeof t.welcome=="string"&&t.welcome.length>0&&(n.welcome=t.welcome),Array.isArray(t.quicks)){let r=Q(t.quicks);(r.length>0||t.quicks.length===0)&&(n.quicks=r)}return n.welcome===void 0&&n.quicks===void 0?null:n}function R(e,t){if(typeof e!="object"||e===null)return null;let n=e;return n.source!==y||n.version!==1||typeof n.type!="string"||!t.includes(n.type)?null:{type:n.type,payload:n.payload}}var p=s(b()),P=/^[A-Za-z0-9._:@-]{1,128}$/,q="Open this chat from the host page embed to start chatting.",v="axiom-embed-root",C="axiom-embed-style",A=32768,G=`
1
+ import{b as f}from"./embed-page-chunk-QCKYIPFY.js";import"./embed-page-chunk-MFFCCHGW.js";import"./embed-page-chunk-T7XXE35M.js";import{a as b}from"./embed-page-chunk-MB65DZAX.js";import"./embed-page-chunk-VW3NF5NT.js";import"./embed-page-chunk-5JDQ456X.js";import"./embed-page-chunk-6KCBXVIT.js";import{a as g}from"./embed-page-chunk-FZRTVFHF.js";import"./embed-page-chunk-4BKXE5ON.js";import"./embed-page-chunk-IX4WLWCY.js";import"./embed-page-chunk-EUOOO2LS.js";import{b as U,e as s}from"./embed-page-chunk-6Y3UYJ6Z.js";var h=U(d=>{"use strict";var E=g();d.createRoot=E.createRoot,d.hydrateRoot=E.hydrateRoot;var ee});var T=s(h()),k=s(g());var y="axiom-web-app-embed";var x=["axiom:hello","axiom:identity","axiom:send","axiom:config"];function w(e,t){return{source:y,version:1,type:e,payload:t}}var N=6,z=40,B=500;function H(e){if(typeof e!="object"||e===null||Array.isArray(e))return!1;let t=e,n=Object.keys(t);return n.length!==2||!n.includes("label")||!n.includes("text")?!1:typeof t.label=="string"&&t.label.length>=1&&t.label.length<=z&&typeof t.text=="string"&&t.text.length>=1&&t.text.length<=B}function Q(e){if(!Array.isArray(e))return[];let t=[];for(let n of e){if(t.length>=N)break;H(n)&&t.push({label:n.label,text:n.text})}return t}function _(e){if(typeof e!="object"||e===null)return null;let t=e,n={};if(typeof t.welcome=="string"&&t.welcome.length>0&&(n.welcome=t.welcome),Array.isArray(t.quicks)){let r=Q(t.quicks);(r.length>0||t.quicks.length===0)&&(n.quicks=r)}return n.welcome===void 0&&n.quicks===void 0?null:n}function R(e,t){if(typeof e!="object"||e===null)return null;let n=e;return n.source!==y||n.version!==1||typeof n.type!="string"||!t.includes(n.type)?null:{type:n.type,payload:n.payload}}var p=s(b()),P=/^[A-Za-z0-9._:@-]{1,128}$/,q="Open this chat from the host page embed to start chatting.",v="axiom-embed-root",C="axiom-embed-style",A=32768,G=`
2
2
  html, body, #${v} { height: 100%; margin: 0; }
3
3
  .axiom-embed-fill { height: 100% !important; min-height: 0 !important; }
4
4
  .axiom-embed-card { display: flex; align-items: center; justify-content: center; height: 100%; margin: 0; font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: 14px; color: #64748b; padding: 24px; box-sizing: border-box; text-align: center; }
package/dist/index.d.mts CHANGED
@@ -1,7 +1,8 @@
1
1
  import * as http from 'http';
2
- import { ProjectRoomMessage, ProjectBotMembership, ProjectMembership, TaskItem, ChannelAdapter, InboundMessage, Binding, BindingRegistry, ChannelInstallationStore, DispatchResult, ProjectRoomEventScope, ProjectRoomBusinessEventDraft, ProjectRoomScopedBusinessEvent, ProjectRoomBusinessEvent, ProjectRoomEventSubscription, TaskStore, ProjectRoomStore, ProjectBotMembershipStore, ThreadStore, ProjectRoom, Thread, ProjectStore, TaskWorkItemStore, TenantStore, OutboxStore, LoggerType, PinoFileOptions } from '@axiom-lattice/protocols';
2
+ import { ProjectRoomMessage, ProjectBotMembership, ProjectMembership, TaskItem, ChannelAdapter, InboundMessage, Binding, BindingRegistry, ChannelInstallationStore, DispatchResult, ModelProviderStore, UserTenantLinkStore, LLMConfig, ModelProviderPublicEntry, TenantModelCatalog, CreateModelProviderRequest, UpdateModelProviderRequest, ModelDiscoveryResult, ModelProviderProbeRequest, ModelProviderProbeResult, ModelSelection, ProjectRoomEventScope, ProjectRoomBusinessEventDraft, ProjectRoomScopedBusinessEvent, ProjectRoomBusinessEvent, ProjectRoomEventSubscription, TaskStore, ProjectRoomStore, ProjectBotMembershipStore, ThreadStore, ProjectRoom, Thread, ProjectStore, TaskWorkItemStore, TenantStore, OutboxStore, A2AApiKeyStore, AssistantStore, A2AApiKeyEntry, A2AAuthContext, Assistant, OpenGrant, A2AApiKeyRecord, LoggerType, PinoFileOptions } from '@axiom-lattice/protocols';
3
3
  import { TrustedAgentMessageDispatcher, TrustedMessageOptions, TaskLifecycleListener, TaskLifecycleService, IMessageQueueStore, Agent, TaskRunRegistry } from '@axiom-lattice/core';
4
4
  import * as fastify from 'fastify';
5
+ import { RouteGenericInterface, FastifyRequest, FastifyReply } from 'fastify';
5
6
 
6
7
  /** Public publisher surface for committed Project Room business facts. */
7
8
  interface ProjectRoomRealtimePublisher {
@@ -166,6 +167,252 @@ declare class MessageRouter {
166
167
  private projectRoomInterrupt;
167
168
  }
168
169
 
170
+ /**
171
+ * Tenant model provider service.
172
+ *
173
+ * Owns provider configuration CRUD, upstream discovery, and the tenant-scoped
174
+ * model catalog. All tenant scoping decisions live here; the controller is a
175
+ * thin HTTP shell.
176
+ */
177
+
178
+ /** Narrow persistence and registry dependencies of the service. */
179
+ interface ModelProviderServiceDeps {
180
+ providerStore: ModelProviderStore;
181
+ userTenantLinkStore: Pick<UserTenantLinkStore, "getLink">;
182
+ registry: {
183
+ register(tenantId: string, modelKey: string, config: LLMConfig): void;
184
+ has(tenantId: string, modelKey: string): boolean;
185
+ remove(tenantId: string, modelKey: string): boolean;
186
+ listRegistered(tenantId: string): string[];
187
+ };
188
+ idFactory?: () => string;
189
+ /**
190
+ * Best-effort local change notification for cross-replica invalidation.
191
+ * Called synchronously after a successful mutating operation.
192
+ */
193
+ onChanged?: (tenantId: string, providerId: string) => void;
194
+ }
195
+ /**
196
+ * Service for tenant-scoped model provider configuration and discovery.
197
+ */
198
+ declare class ModelProviderService {
199
+ private readonly deps;
200
+ private readonly idFactory;
201
+ constructor(deps: ModelProviderServiceDeps);
202
+ /**
203
+ * List the tenant's provider configurations.
204
+ */
205
+ list(userId: string | undefined, tenantId: string): Promise<ModelProviderPublicEntry[]>;
206
+ /**
207
+ * Read one provider configuration.
208
+ */
209
+ get(userId: string | undefined, tenantId: string, id: string): Promise<ModelProviderPublicEntry>;
210
+ /**
211
+ * The full model catalog visible to a tenant.
212
+ */
213
+ catalog(userId: string | undefined, tenantId: string): Promise<TenantModelCatalog>;
214
+ /**
215
+ * Hydrates the runtime model registry from the persisted provider catalog.
216
+ *
217
+ * @returns The model keys registered in this process.
218
+ * @remarks Registers the Phase-1 (default tenant) enabled provider models so a
219
+ * replica that did not perform discovery — or that restarted — can execute
220
+ * them. Best-effort; the caller logs failures.
221
+ */
222
+ hydrateRegistry(): Promise<string[]>;
223
+ /**
224
+ * Bring this replica's registry in line with one provider's current state.
225
+ *
226
+ * @param tenantId Tenant scope (only the default tenant registers).
227
+ * @param providerId Provider to reconcile; omit to re-hydrate the tenant.
228
+ * @remarks A missing provider removes every registry key under its
229
+ * `${providerId}/` prefix; legacy keys registered through `/api/models` are
230
+ * left untouched.
231
+ */
232
+ reconcileProvider(tenantId: string, providerId?: string): Promise<void>;
233
+ /**
234
+ * Create a provider configuration. Does not run discovery.
235
+ */
236
+ create(userId: string | undefined, tenantId: string, input: CreateModelProviderRequest): Promise<ModelProviderPublicEntry>;
237
+ /**
238
+ * Update a provider configuration.
239
+ * An omitted or empty `apiKey` keeps the stored key.
240
+ */
241
+ update(userId: string | undefined, tenantId: string, id: string, updates: Partial<UpdateModelProviderRequest>): Promise<ModelProviderPublicEntry>;
242
+ /**
243
+ * Delete a provider configuration, its snapshot, and any registry entries
244
+ * that no other provider in the tenant still supplies.
245
+ */
246
+ remove(userId: string | undefined, tenantId: string, id: string): Promise<void>;
247
+ /**
248
+ * Discover the provider's models and replace its snapshot.
249
+ */
250
+ discover(userId: string | undefined, tenantId: string, id: string): Promise<ModelDiscoveryResult>;
251
+ /**
252
+ * Bring the execution registry in line with a provider's current state.
253
+ *
254
+ * Registration copies `apiKey` and `baseURL` by value, so a rotated
255
+ * credential or a changed endpoint only takes effect once the model is
256
+ * re-registered. Disabled models — and every model of a disabled provider —
257
+ * are removed instead.
258
+ *
259
+ * @returns The model keys left registered
260
+ */
261
+ /**
262
+ * Probe an upstream without touching the store.
263
+ *
264
+ * Powers the provider form's "test latency" and "list models" actions, which
265
+ * validate a base URL and API key the user has typed but not yet saved. The
266
+ * URL is validated exactly as on create/update and is never persisted, so the
267
+ * "discover resolves its URL from the store" rule is not weakened — nothing
268
+ * a probe learns can reach the snapshot.
269
+ */
270
+ probe(userId: string | undefined, tenantId: string, input: ModelProviderProbeRequest): Promise<ModelProviderProbeResult>;
271
+ /**
272
+ * Set exactly which models this provider contributes.
273
+ *
274
+ * This is the "add model" action: the caller passes the ids it picked from
275
+ * the live list, and the provider's model set becomes precisely those. Ids
276
+ * are expected to have come from a probe — model names cannot be guessed,
277
+ * since a wrong id surfaces later as "model not found" or an authorisation
278
+ * error rather than at configuration time.
279
+ */
280
+ setModels(userId: string | undefined, tenantId: string, id: string, selection: ModelSelection[]): Promise<ModelProviderPublicEntry>;
281
+ private syncRegistry;
282
+ /** Best-effort local mutation notification; never affects the operation. */
283
+ private notifyChanged;
284
+ /**
285
+ * Enable or disable a single discovered model.
286
+ */
287
+ setModelEnabled(userId: string | undefined, tenantId: string, id: string, modelId: string, enabled: boolean): Promise<ModelProviderPublicEntry>;
288
+ /**
289
+ * Whether discovered models should be registered for execution.
290
+ */
291
+ private shouldRegister;
292
+ /**
293
+ * Build the runtime LLM config for a discovered model.
294
+ *
295
+ * `apiKeyEnvName`, `extra` and `modelKwargs` are deliberately omitted — they
296
+ * would let a tenant name an arbitrary process environment variable to read
297
+ * credentials from, or inject arbitrary constructor options.
298
+ */
299
+ private toLlmConfig;
300
+ /**
301
+ * The sole projection from an internal provider entry to an API-safe shape.
302
+ */
303
+ private toPublicEntry;
304
+ /**
305
+ * Reject a provider that would end up without a credential.
306
+ *
307
+ * @param apiKey - The key the provider would hold, if any
308
+ * @throws {ModelProviderServiceError} When no key is present
309
+ */
310
+ private assertHasApiKey;
311
+ /**
312
+ * Load a provider or fail with 404.
313
+ *
314
+ * Callers always pass the session's tenant, so a provider belonging to
315
+ * another tenant is indistinguishable from a missing one.
316
+ */
317
+ private requireProvider;
318
+ /**
319
+ * Require that the caller belongs to the tenant.
320
+ * An absent userId means the unauthenticated development fallback.
321
+ */
322
+ private assertMember;
323
+ /**
324
+ * Require that the caller is a tenant owner or admin.
325
+ * Roles are read from the tenant link, never from the session token.
326
+ */
327
+ private assertAdmin;
328
+ /**
329
+ * Validate the shape of an upstream URL and translate failures into service
330
+ * errors.
331
+ *
332
+ * Addresses are deliberately not restricted: other tenant-configured URLs in
333
+ * this platform (metrics servers, MCP servers) reach wherever they point, and
334
+ * self-hosted model servers normally sit on the gateway's own network.
335
+ *
336
+ * The mapping is explicit so the public error codes stay decoupled from the
337
+ * reasons the URL utility happens to use internally.
338
+ */
339
+ private assertSafeUrl;
340
+ /**
341
+ * Translate a store-level unique violation into a stable service error.
342
+ */
343
+ private mapStoreError;
344
+ }
345
+
346
+ /**
347
+ * HTTP controller for tenant model providers.
348
+ *
349
+ * Thin shell: resolves the tenant scope from the authenticated session, parses
350
+ * the request, delegates to ModelProviderService, and renders service errors.
351
+ */
352
+
353
+ type Request<T extends RouteGenericInterface = RouteGenericInterface> = FastifyRequest<T> & {
354
+ user?: {
355
+ id?: string;
356
+ tenantId?: string;
357
+ };
358
+ };
359
+ /** Controller for tenant-scoped model provider management. */
360
+ declare class ModelProviderController {
361
+ private readonly service;
362
+ private readonly authRequired;
363
+ constructor(deps: ModelProviderServiceDeps, options?: {
364
+ authRequired?: boolean;
365
+ });
366
+ /** GET /api/tenant-models */
367
+ getCatalog(request: Request, reply: FastifyReply): Promise<unknown>;
368
+ /** Hydrates the runtime model registry from persisted providers (startup). */
369
+ hydrateRegistry(): Promise<string[]>;
370
+ /** Reconciles one provider (or the tenant) after a foreign change. */
371
+ reconcileProvider(tenantId: string, providerId?: string): Promise<void>;
372
+ /** GET /api/model-providers */
373
+ listProviders(request: Request, reply: FastifyReply): Promise<unknown>;
374
+ /** GET /api/model-providers/:providerId */
375
+ getProvider(request: Request<{
376
+ Params: {
377
+ providerId: string;
378
+ };
379
+ }>, reply: FastifyReply): Promise<unknown>;
380
+ /** POST /api/model-providers */
381
+ createProvider(request: Request, reply: FastifyReply): Promise<unknown>;
382
+ /** PUT /api/model-providers/:providerId */
383
+ updateProvider(request: Request<{
384
+ Params: {
385
+ providerId: string;
386
+ };
387
+ }>, reply: FastifyReply): Promise<unknown>;
388
+ /** DELETE /api/model-providers/:providerId */
389
+ deleteProvider(request: Request<{
390
+ Params: {
391
+ providerId: string;
392
+ };
393
+ }>, reply: FastifyReply): Promise<unknown>;
394
+ /** POST /api/model-providers/probe */
395
+ probeProvider(request: Request, reply: FastifyReply): Promise<unknown>;
396
+ /** PUT /api/model-providers/:providerId/models */
397
+ setProviderModels(request: Request<{
398
+ Params: {
399
+ providerId: string;
400
+ };
401
+ }>, reply: FastifyReply): Promise<unknown>;
402
+ /** POST /api/model-providers/:providerId/discover */
403
+ discoverProvider(request: Request<{
404
+ Params: {
405
+ providerId: string;
406
+ };
407
+ }>, reply: FastifyReply): Promise<unknown>;
408
+ /** POST /api/model-providers/:providerId/models/enabled */
409
+ setModelEnabled(request: Request<{
410
+ Params: {
411
+ providerId: string;
412
+ };
413
+ }>, reply: FastifyReply): Promise<unknown>;
414
+ }
415
+
169
416
  type Listener = (event: ProjectRoomBusinessEvent) => void | Promise<void>;
170
417
  /** Construction options for the bounded process-local Project Room event broker. */
171
418
  interface ProjectRoomEventBrokerOptions {
@@ -462,6 +709,26 @@ interface ProjectTaskExecutionOptions {
462
709
  /** Queue message id factory; defaults to `randomUUID`. */
463
710
  idFactory?: () => string;
464
711
  }
712
+ /**
713
+ * Optional resume command threaded into the queued trusted run message.
714
+ *
715
+ * @remarks Only honored for `resume`-mode dispatch of an interrupted task:
716
+ * a paused LangGraph worker needs `Command(resume)` to continue the same run.
717
+ * A `start` (pending) or restart dispatch never attaches it, even when a
718
+ * caller passes options.
719
+ */
720
+ interface ProjectTaskResumeOptions {
721
+ command?: {
722
+ resume: unknown;
723
+ };
724
+ /**
725
+ * Optional status guard evaluated against the same single `requireTask` read
726
+ * that routes the dispatch, closing the race where a task flips between a
727
+ * caller's status check and dispatch (which would otherwise restart without
728
+ * the command and discard the answers).
729
+ */
730
+ expectedStatus?: TaskItem["status"];
731
+ }
465
732
  /** Collaborators required to validate, dispatch, recover, and settle Project Task executions. */
466
733
  interface ProjectTaskExecutionDependencies {
467
734
  tasks: TaskStore;
@@ -473,6 +740,26 @@ interface ProjectTaskExecutionDependencies {
473
740
  dispatcher: TrustedAgentMessageDispatcher;
474
741
  queue: IMessageQueueStore;
475
742
  agentFor: (task: TaskItem, thread: Thread) => Agent;
743
+ /** JIT-materializes a public-project task owner so it has a room anchor to post/reply. */
744
+ ensurePublicOwner?: (input: {
745
+ tenantId: string;
746
+ projectId: string;
747
+ assistantId: string;
748
+ }) => Promise<unknown>;
749
+ /**
750
+ * Optional gateway hook: a task thread paused with a durable interruption.
751
+ *
752
+ * @remarks Best-effort and fire-and-forget: it never blocks or alters
753
+ * settlement. It may fire more than once for the same interrupt (the
754
+ * `message:interrupted` event path and the recovery path), so consumers must
755
+ * dedupe using the correlation key derived from `messageId`.
756
+ */
757
+ onTaskInterrupted?: (input: {
758
+ task: TaskItem;
759
+ thread: Thread;
760
+ messageId: string;
761
+ state: unknown;
762
+ }) => void | Promise<void>;
476
763
  }
477
764
  /** Input for recovering exactly one persisted Project Task queue execution. */
478
765
  interface RecoverProjectTaskExecutionInput {
@@ -510,7 +797,7 @@ declare class ProjectTaskExecutionService {
510
797
  tenantId: string;
511
798
  taskId: string;
512
799
  actor?: string;
513
- }, signal?: AbortSignal): Promise<ProjectTaskRunOutcome>;
800
+ }, signal?: AbortSignal, options?: ProjectTaskResumeOptions): Promise<ProjectTaskRunOutcome>;
514
801
  /** Clears the durable interruption and loads the exact resumed task. */
515
802
  private prepareResume;
516
803
  /**
@@ -519,6 +806,7 @@ declare class ProjectTaskExecutionService {
519
806
  *
520
807
  * @param input Tenant, task identity, and optional command actor.
521
808
  * @param signal Optional cancellation signal for the armed settlement.
809
+ * @param options Optional resume command carried into the queued run message.
522
810
  * @returns The settled run outcome.
523
811
  * @remarks `start` semantics: make this task run. `pending` starts,
524
812
  * `interrupted` resumes, `failed` retries, and `completed`/`cancelled`
@@ -526,17 +814,19 @@ declare class ProjectTaskExecutionService {
526
814
  * conflict. This is a record transition plus an application-layer side
527
815
  * effect; the kernel only records the status change. `actor` attributes the
528
816
  * start/resume/retry/reopen transition and defaults to the system actor.
817
+ * `options.command` is only carried on an interrupted-task resume.
529
818
  */
530
819
  dispatch(input: {
531
820
  tenantId: string;
532
821
  taskId: string;
533
822
  actor?: string;
534
- }, signal?: AbortSignal): Promise<ProjectTaskRunOutcome>;
823
+ }, signal?: AbortSignal, options?: ProjectTaskResumeOptions): Promise<ProjectTaskRunOutcome>;
535
824
  /**
536
825
  * Dispatches a Project Task without awaiting its settlement.
537
826
  *
538
827
  * @param input Tenant, task identity, and optional command actor.
539
828
  * @param signal Optional cancellation signal for the pre-run transition.
829
+ * @param options Optional resume command carried into the queued run message.
540
830
  * @returns The `in_progress` task snapshot and whether the run was queued.
541
831
  * @remarks Intended for command HTTP handlers that must release the Project
542
832
  * mutation mutex immediately: the armed settlement keeps running in the
@@ -548,7 +838,7 @@ declare class ProjectTaskExecutionService {
548
838
  tenantId: string;
549
839
  taskId: string;
550
840
  actor?: string;
551
- }, signal?: AbortSignal): Promise<{
841
+ }, signal?: AbortSignal, options?: ProjectTaskResumeOptions): Promise<{
552
842
  task: TaskItem;
553
843
  queued: boolean;
554
844
  }>;
@@ -601,6 +891,14 @@ declare class ProjectTaskExecutionService {
601
891
  private compensateCancellation;
602
892
  private reloadTask;
603
893
  private settleInterruptedEvent;
894
+ /**
895
+ * Fire-and-forget best-effort projection of a durable interruption.
896
+ *
897
+ * @remarks Never awaited and never allowed to affect settlement. The state is
898
+ * resolved lazily so a missing/undefined event state only triggers a thread
899
+ * read when the hook is actually wired.
900
+ */
901
+ private emitInterrupted;
604
902
  /** Reads the paused thread snapshot when no interrupt event payload is available (recovery). */
605
903
  private readInterruptedThreadState;
606
904
  /**
@@ -704,6 +1002,8 @@ interface LatticeRouteResult {
704
1002
  projectTaskUnavailableReason?: string;
705
1003
  /** Present only in exact single-Gateway realtime mode. */
706
1004
  projectRoomRealtimeRuntime?: ProjectRoomRealtimeRuntime;
1005
+ /** Always-present controller used by the outbox poller to reconcile foreign changes. */
1006
+ modelProviderController: ModelProviderController;
707
1007
  }
708
1008
 
709
1009
  declare const defaultSwaggerConfig: {
@@ -1116,6 +1416,122 @@ declare function publishRoomEvent(outbox: OutboxStore, owner: string, scope: Pro
1116
1416
  */
1117
1417
  declare function ingestRoomEvent(payload: unknown, broker: ProjectRoomEventBroker): boolean;
1118
1418
 
1419
+ /** Outbox topic carrying cross-replica A2A API-key invalidation. */
1420
+ declare const A2A_KEY_CHANGED_TOPIC = "a2a-key-changed";
1421
+ /**
1422
+ * Best-effort publish of an A2A key change to the durable outbox.
1423
+ *
1424
+ * @param outbox Shared outbox store.
1425
+ * @param owner This replica id (recorded as the event origin).
1426
+ * @param tenantId Tenant whose keys changed.
1427
+ * @remarks Never throws; peers fall back to startup load / the next change.
1428
+ */
1429
+ declare function publishA2AKeyChanged(outbox: OutboxStore, owner: string, tenantId: string): Promise<void>;
1430
+ /**
1431
+ * Reloads this replica's key map after a foreign key change.
1432
+ *
1433
+ * @param payload Outbox payload, expected to carry a non-empty `tenantId`.
1434
+ * @param reload Store-backed reload callback (refreshes the global key map).
1435
+ */
1436
+ declare function handleA2AKeyChanged(payload: unknown, reload: () => Promise<void>): Promise<void>;
1437
+
1438
+ /** Outbox topic carrying cross-replica model-provider invalidation. */
1439
+ declare const MODEL_PROVIDER_CHANGED_TOPIC = "model-provider-changed";
1440
+ /**
1441
+ * Best-effort publish of a model provider change to the durable outbox.
1442
+ *
1443
+ * @param outbox Shared outbox store.
1444
+ * @param owner This replica id (recorded as the event origin).
1445
+ * @param tenantId Tenant that owns the changed provider.
1446
+ * @param providerId Provider that changed; omitted to reconcile the tenant.
1447
+ * @remarks Never throws; startup hydration / the next change corrects a miss.
1448
+ */
1449
+ declare function publishModelProviderChanged(outbox: OutboxStore, owner: string, tenantId: string, providerId?: string): Promise<void>;
1450
+ /**
1451
+ * Reconciles one provider (or the whole tenant) after a foreign change.
1452
+ *
1453
+ * @param payload Outbox payload, expected to carry a non-empty `tenantId`.
1454
+ * @param reconcile Store-backed reconcile callback.
1455
+ */
1456
+ declare function handleModelProviderChanged(payload: unknown, reconcile: (tenantId: string, providerId?: string) => Promise<void>): Promise<void>;
1457
+
1458
+ /**
1459
+ * Dependencies injected into {@link A2AAuthService} so stores can be mocked in tests.
1460
+ */
1461
+ interface A2AAuthServiceDeps {
1462
+ keyStore: A2AApiKeyStore;
1463
+ assistantStore: AssistantStore;
1464
+ projectStore: ProjectStore;
1465
+ envKeyMap?: Map<string, A2AApiKeyEntry>;
1466
+ /**
1467
+ * When true, requests with no key are allowed through when no keys are
1468
+ * configured (dev mode). Defaults to false — JSON-RPC stays locked until a
1469
+ * key is configured or this opt-in is explicitly enabled.
1470
+ */
1471
+ allowUnauthenticated?: boolean;
1472
+ }
1473
+ interface UpdateKeyScopedInput {
1474
+ label?: string;
1475
+ projectId?: string;
1476
+ assistantIds?: string[];
1477
+ grants?: OpenGrant[];
1478
+ }
1479
+ /** Input for creating an API key scoped to a tenant + project. */
1480
+ interface CreateKeyScopedInput {
1481
+ tenantId: string;
1482
+ projectId?: string;
1483
+ assistantIds?: string[];
1484
+ /** Open-door grants (validated minimally; unknown domains simply never match). */
1485
+ grants?: OpenGrant[];
1486
+ label?: string;
1487
+ }
1488
+ /**
1489
+ * Parse an `A2A_API_KEYS="key:tenant:project,..."` env string into a lookup map.
1490
+ * Entries missing a key, tenant, or project segment are skipped.
1491
+ */
1492
+ declare function parseEnvKeys(env: string): Map<string, A2AApiKeyEntry>;
1493
+ /**
1494
+ * Authenticates A2A requests against the key store + env key map,
1495
+ * enforces assistant whitelist / exposure, and validates key scope on creation.
1496
+ */
1497
+ declare class A2AAuthService {
1498
+ private readonly keyStore;
1499
+ private readonly assistantStore;
1500
+ private readonly projectStore;
1501
+ private readonly envKeyMap;
1502
+ /** Lazily-populated store key map; reloaded via {@link refresh} after keys CRUD. */
1503
+ private storeKeyMapCache;
1504
+ private readonly allowUnauthenticated;
1505
+ constructor(deps: A2AAuthServiceDeps);
1506
+ /**
1507
+ * Reload the cached store key map. Call once at startup and after any keys
1508
+ * CRUD mutation so {@link authenticate} does not hit the store on every request.
1509
+ */
1510
+ refresh(): Promise<void>;
1511
+ /** Return the cached store key map, loading it on first use. */
1512
+ private getStoreKeyMap;
1513
+ /**
1514
+ * Authenticate an incoming request header (authorization or x-api-key value).
1515
+ * Returns an auth context; `authenticated: false` means the caller must reject.
1516
+ */
1517
+ authenticate(header: string | undefined): Promise<A2AAuthContext>;
1518
+ /**
1519
+ * Assert that the given assistant is reachable under the provided auth
1520
+ * context: authenticated, same tenant, and within the key's whitelist.
1521
+ *
1522
+ * Authorization is unified on the API key — the agent no longer carries an
1523
+ * exposure flag; the key's assistant whitelist / grants is the single source.
1524
+ */
1525
+ assertAgentAccess(auth: A2AAuthContext, assistantId: string): Promise<Assistant>;
1526
+ /**
1527
+ * Create an API key scoped to a tenant + project, validating the project exists
1528
+ * in the tenant and every assistant belongs to the same tenant.
1529
+ */
1530
+ createKeyScoped(input: CreateKeyScopedInput): Promise<A2AApiKeyRecord>;
1531
+ updateKeyScoped(id: string, tenantId: string, input: UpdateKeyScopedInput): Promise<A2AApiKeyRecord>;
1532
+ private extractToken;
1533
+ }
1534
+
1119
1535
  /**
1120
1536
  * Logger configuration for gateway
1121
1537
  */
@@ -1135,6 +1551,16 @@ interface LatticeGatewayConfig {
1135
1551
  defaultStartPollingQueue: boolean;
1136
1552
  };
1137
1553
  loggerConfig?: Partial<GatewayLoggerConfig>;
1554
+ /**
1555
+ * Install SIGTERM/SIGINT graceful-shutdown handlers (default `true`). On a
1556
+ * signal the gateway aborts in-flight thread runs (releasing their
1557
+ * cross-replica `thread-run` leases so a peer can take over immediately),
1558
+ * stops background jobs, and closes the HTTP server before exiting.
1559
+ * Set `false` when the host manages its own shutdown.
1560
+ */
1561
+ gracefulShutdown?: boolean;
1562
+ /** Force-exit deadline for graceful shutdown in ms (default 10_000). */
1563
+ gracefulShutdownTimeoutMs?: number;
1138
1564
  }
1139
1565
  declare const LatticeGateway: {
1140
1566
  startAsHttpEndpoint: (config?: LatticeGatewayConfig) => Promise<void>;
@@ -1146,4 +1572,4 @@ declare const LatticeGateway: {
1146
1572
  AgentTaskConsumer: typeof AgentTaskConsumer;
1147
1573
  };
1148
1574
 
1149
- export { type GatewayLoggerConfig, LatticeGateway, type LatticeGatewayConfig, type OrphanRecoveryRuntime, type OrphanRecoveryRuntimeDeps, ProjectRoomEventBroker, ROOM_EVENT_TOPIC, type RecoverTaskDeps, type RecoverTaskResult, type TaskLivenessProbe, createOrphanRecoveryRuntime, createRecoveryCallerDeps, createTaskRunLiveness, ingestRoomEvent, publishRoomEvent, recoverTask, recoverTopCaller, resolveTopCaller, runOrphanRecovery };
1575
+ export { A2AAuthService, A2A_KEY_CHANGED_TOPIC, type GatewayLoggerConfig, LatticeGateway, type LatticeGatewayConfig, MODEL_PROVIDER_CHANGED_TOPIC, ModelProviderService, type ModelProviderServiceDeps, type OrphanRecoveryRuntime, type OrphanRecoveryRuntimeDeps, ProjectRoomEventBroker, ROOM_EVENT_TOPIC, type RecoverTaskDeps, type RecoverTaskResult, type TaskLivenessProbe, createOrphanRecoveryRuntime, createRecoveryCallerDeps, createTaskRunLiveness, handleA2AKeyChanged, handleModelProviderChanged, ingestRoomEvent, parseEnvKeys, publishA2AKeyChanged, publishModelProviderChanged, publishRoomEvent, recoverTask, recoverTopCaller, resolveTopCaller, runOrphanRecovery };