feat(gateway): register Hermes runtime provider (#740)
This commit was merged in pull request #740.
This commit is contained in:
121
apps/gateway/src/agent/hermes-runtime.transport.ts
Normal file
121
apps/gateway/src/agent/hermes-runtime.transport.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import type { HermesLegacySession, HermesRuntimeTransport } from '@mosaicstack/agent';
|
||||
import type {
|
||||
RuntimeAttachHandle,
|
||||
RuntimeAttachMode,
|
||||
RuntimeMessage,
|
||||
RuntimeScope,
|
||||
RuntimeStreamEvent,
|
||||
} from '@mosaicstack/types';
|
||||
|
||||
/** Concrete HTTP transport for a configured legacy Hermes runtime endpoint. */
|
||||
export class GatewayHermesRuntimeTransport implements HermesRuntimeTransport {
|
||||
constructor(
|
||||
private readonly baseUrl = process.env['MOSAIC_HERMES_RUNTIME_URL']?.trim(),
|
||||
private readonly serviceToken = process.env['MOSAIC_HERMES_RUNTIME_TOKEN']?.trim(),
|
||||
private readonly fetchFn: typeof fetch = fetch,
|
||||
) {}
|
||||
|
||||
async capabilities(scope: RuntimeScope): Promise<string[]> {
|
||||
return this.request<string[]>('/capabilities', scope);
|
||||
}
|
||||
|
||||
async health(scope: RuntimeScope): Promise<{ status: string; detail?: string }> {
|
||||
return this.request<{ status: string; detail?: string }>('/health', scope);
|
||||
}
|
||||
|
||||
async sessions(scope: RuntimeScope): Promise<HermesLegacySession[]> {
|
||||
return this.request<HermesLegacySession[]>('/sessions', scope);
|
||||
}
|
||||
|
||||
async *stream(
|
||||
sessionId: string,
|
||||
cursor: string | undefined,
|
||||
scope: RuntimeScope,
|
||||
): AsyncIterable<RuntimeStreamEvent> {
|
||||
const params = new URLSearchParams(cursor ? { cursor } : {});
|
||||
const events = await this.request<RuntimeStreamEvent[]>(
|
||||
`/sessions/${encodeURIComponent(sessionId)}/stream?${params.toString()}`,
|
||||
scope,
|
||||
);
|
||||
yield* events;
|
||||
}
|
||||
|
||||
async send(sessionId: string, message: RuntimeMessage, scope: RuntimeScope): Promise<void> {
|
||||
await this.request(`/sessions/${encodeURIComponent(sessionId)}/messages`, scope, {
|
||||
method: 'POST',
|
||||
body: message,
|
||||
});
|
||||
}
|
||||
|
||||
async attach(
|
||||
sessionId: string,
|
||||
mode: RuntimeAttachMode,
|
||||
scope: RuntimeScope,
|
||||
): Promise<RuntimeAttachHandle> {
|
||||
return this.request<RuntimeAttachHandle>(
|
||||
`/sessions/${encodeURIComponent(sessionId)}/attach`,
|
||||
scope,
|
||||
{
|
||||
method: 'POST',
|
||||
body: { mode },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async detach(attachmentId: string, scope: RuntimeScope): Promise<void> {
|
||||
await this.request(`/attachments/${encodeURIComponent(attachmentId)}`, scope, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
async terminate(sessionId: string, approvalRef: string, scope: RuntimeScope): Promise<void> {
|
||||
await this.request(`/sessions/${encodeURIComponent(sessionId)}/terminate`, scope, {
|
||||
method: 'POST',
|
||||
body: { approvalRef },
|
||||
});
|
||||
}
|
||||
|
||||
private async request<T>(
|
||||
path: string,
|
||||
scope: RuntimeScope,
|
||||
init: { method?: string; body?: unknown } = {},
|
||||
): Promise<T> {
|
||||
if (!this.baseUrl || !this.serviceToken) {
|
||||
throw new Error(
|
||||
'MOSAIC_HERMES_RUNTIME_URL and MOSAIC_HERMES_RUNTIME_TOKEN must configure Hermes transport',
|
||||
);
|
||||
}
|
||||
const endpoint = new URL(this.baseUrl);
|
||||
if (endpoint.protocol !== 'https:' && !isLoopbackHttp(endpoint)) {
|
||||
throw new Error('Hermes runtime transport requires HTTPS outside loopback');
|
||||
}
|
||||
const response = await this.fetchFn(
|
||||
new URL(path.replace(/^\//, ''), `${endpoint.toString().replace(/\/$/, '')}/`),
|
||||
{
|
||||
method: init.method ?? 'GET',
|
||||
headers: {
|
||||
accept: 'application/json',
|
||||
authorization: `Bearer ${this.serviceToken}`,
|
||||
'x-mosaic-actor-id': scope.actorId,
|
||||
'x-mosaic-tenant-id': scope.tenantId,
|
||||
'x-mosaic-channel-id': scope.channelId,
|
||||
'x-correlation-id': scope.correlationId,
|
||||
...(init.body ? { 'content-type': 'application/json' } : {}),
|
||||
},
|
||||
...(init.body ? { body: JSON.stringify(init.body) } : {}),
|
||||
},
|
||||
);
|
||||
if (!response.ok) throw new Error(`Hermes runtime request failed: ${response.status}`);
|
||||
if (response.status === 204) return undefined as T;
|
||||
return (await response.json()) as T;
|
||||
}
|
||||
}
|
||||
|
||||
function isLoopbackHttp(endpoint: URL): boolean {
|
||||
return (
|
||||
endpoint.protocol === 'http:' &&
|
||||
(endpoint.hostname === 'localhost' ||
|
||||
endpoint.hostname === '127.0.0.1' ||
|
||||
endpoint.hostname === '::1')
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user