@struktoai/mirage-core
Version:
A Unified Virtual Filesystem For AI Agents
133 lines • 5.06 kB
JavaScript
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. =========
import { apiRequest, buildUrl } from "../api/client.js";
import { ApiError } from "../api/errors.js";
const TRACE_ID_RE = /^[0-9a-f]{16}$|^[0-9a-f]{32}$/i;
// Jaeger's own query service self-instruments, so a `jaeger` service shows up
// in listings alongside the ones a user actually sent.
export class JaegerApiError extends ApiError {
constructor(message, status = null) {
super(message, status);
this.name = 'JaegerApiError';
}
}
/**
* Report whether a name is a syntactically valid Jaeger trace id.
*
* Checked before calling the API so a malformed id becomes ENOENT instead of
* the API's 400 "invalid length for TraceID".
*/
export function isTraceId(value) {
return TRACE_ID_RE.test(value);
}
// The parse comes before the status check, so a non-JSON error body reports
// as invalid JSON rather than a bare status, matching the old transport.
function errorOf(response, text) {
let body;
try {
body = JSON.parse(text);
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
return new JaegerApiError(`Jaeger API: invalid JSON: ${msg}`, response.status);
}
let message = `Jaeger API error: HTTP ${String(response.status)}`;
if (body !== null && typeof body === 'object') {
const errors = body.errors;
if (Array.isArray(errors) && errors.length > 0) {
const first = errors[0];
const msg = first?.msg;
if (typeof msg === 'string' && msg !== '')
message = msg;
}
}
return new JaegerApiError(message, response.status);
}
export class HttpJaegerTransport {
fetch = globalThis.fetch.bind(globalThis);
host;
timeout;
constructor(opts = {}) {
this.host = opts.host ?? 'http://localhost:16686';
this.timeout = opts.timeout;
}
async request(path, query = {}) {
const text = (await apiRequest('GET', buildUrl(this.host, path, query), {
fetchFn: this.fetch,
headers: { Accept: 'application/json' },
timeout: this.timeout,
read: 'text',
errorOf: errorOf,
}));
try {
return JSON.parse(text);
}
catch (err) {
const msg = err instanceof Error ? err.message : String(err);
throw new JaegerApiError(`Jaeger API: invalid JSON: ${msg}`);
}
}
}
function dataList(body) {
if (body === null || typeof body !== 'object')
return [];
const data = body.data;
return Array.isArray(data) ? data : [];
}
function micros(iso, fallback) {
if (iso === undefined || iso === '')
return fallback;
const parsed = Date.parse(iso);
if (Number.isNaN(parsed))
return fallback;
return parsed * 1000;
}
export async function fetchServices(transport) {
const body = await transport.request('/api/services');
return dataList(body).map((name) => String(name));
}
/**
* List operations recorded for a service.
*
* An unknown service yields an empty list rather than an error, so callers
* that need existence semantics must check the service list first.
*/
export async function fetchOperations(transport, service) {
const body = await transport.request('/api/operations', { service });
return dataList(body).filter((row) => row !== null && typeof row === 'object');
}
/**
* Search traces for a service within an explicit time window.
*
* `service` is required by the API and `lookback` is ignored, so the window is
* always sent as explicit microsecond bounds.
*/
export async function fetchTraces(transport, service, opts = {}) {
const body = await transport.request('/api/traces', {
service,
limit: opts.limit ?? 100,
start: micros(opts.fromTimestamp, 0),
end: micros(opts.toTimestamp, Date.now() * 1000),
});
return dataList(body).filter((row) => row !== null && typeof row === 'object');
}
export async function fetchTrace(transport, traceId) {
const body = await transport.request(`/api/traces/${encodeURIComponent(traceId)}`);
const traces = dataList(body).filter((row) => row !== null && typeof row === 'object');
const first = traces[0];
if (first === undefined)
throw new JaegerApiError('trace not found', 404);
return first;
}
//# sourceMappingURL=client.js.map