@muxi-ai/muxi-typescript 0.20260107.0
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.
- package/README.md +67 -0
- package/dist/auth.d.ts +1 -0
- package/dist/auth.js +8 -0
- package/dist/envelope.d.ts +1 -0
- package/dist/envelope.js +19 -0
- package/dist/errors.d.ts +26 -0
- package/dist/errors.js +48 -0
- package/dist/formation.d.ts +89 -0
- package/dist/formation.js +262 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/server.d.ts +37 -0
- package/dist/server.js +60 -0
- package/dist/transport.d.ts +21 -0
- package/dist/transport.js +138 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- package/package.json +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# MUXI TypeScript SDK
|
|
2
|
+
|
|
3
|
+
Official TypeScript/Node SDK for [MUXI](https://muxi.org) — infrastructure for AI agents.
|
|
4
|
+
|
|
5
|
+
**Highlights**
|
|
6
|
+
- Fetch-based HTTP transport (Node 18+), automatic idempotency, SDK/client headers
|
|
7
|
+
- Formation client/admin key auth; server HMAC auth
|
|
8
|
+
- SSE helpers for chat/audio streams and deploy/log tails
|
|
9
|
+
- Retries on 429/5xx/connection errors with exponential backoff
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @muxi-ai/muxi-typescript
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start (server)
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { ServerClient } from "@muxi-ai/muxi-typescript";
|
|
21
|
+
|
|
22
|
+
const server = new ServerClient({
|
|
23
|
+
url: "https://server.example.com",
|
|
24
|
+
keyId: process.env.MUXI_KEY_ID!,
|
|
25
|
+
secretKey: process.env.MUXI_SECRET_KEY!,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log(await server.status());
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start (formation)
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
import { FormationClient } from "@muxi-ai/muxi-typescript";
|
|
35
|
+
|
|
36
|
+
const formation = new FormationClient({
|
|
37
|
+
serverUrl: "https://server.example.com",
|
|
38
|
+
formationId: "your-formation",
|
|
39
|
+
clientKey: process.env.MUXI_CLIENT_KEY!,
|
|
40
|
+
adminKey: process.env.MUXI_ADMIN_KEY!,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
console.log(await formation.health());
|
|
44
|
+
|
|
45
|
+
// Streaming chat (SSE)
|
|
46
|
+
for await (const evt of formation.chatStream({ message: "hello" })) {
|
|
47
|
+
console.log(evt);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Formation base URL override
|
|
53
|
+
|
|
54
|
+
- Default: `serverUrl + /api/{formationId}/v1`
|
|
55
|
+
- Override: set `baseUrl` (e.g., `http://localhost:9012/v1` for direct formation)
|
|
56
|
+
|
|
57
|
+
## Auth & headers
|
|
58
|
+
|
|
59
|
+
- Server: HMAC with `keyId`/`secretKey` on `/rpc/*` calls.
|
|
60
|
+
- Formation: `X-MUXI-CLIENT-KEY` or `X-MUXI-ADMIN-KEY`; optional `X-Muxi-User-ID` via `userId` param.
|
|
61
|
+
- Idempotency: `X-Muxi-Idempotency-Key` auto-generated on every request.
|
|
62
|
+
- SDK/Client headers set automatically (`X-Muxi-SDK`, `X-Muxi-Client`).
|
|
63
|
+
|
|
64
|
+
## Streaming
|
|
65
|
+
|
|
66
|
+
- Chat/audio: `chatStream` / `audioChatStream` return async generators of SSE events.
|
|
67
|
+
- Deploy/log streams: `deployFormationStream`, `updateFormationStream`, `streamFormationLogs`, etc.
|
package/dist/auth.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function buildAuthHeader(keyId: string, secretKey: string, method: string, path: string): string;
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createHmac } from "crypto";
|
|
2
|
+
export function buildAuthHeader(keyId, secretKey, method, path) {
|
|
3
|
+
const timestamp = Math.floor(Date.now() / 1000);
|
|
4
|
+
const signPath = path.split("?", 1)[0];
|
|
5
|
+
const message = `${timestamp};${method};${signPath}`;
|
|
6
|
+
const mac = createHmac("sha256", secretKey).update(message).digest("base64");
|
|
7
|
+
return `MUXI-HMAC key=${keyId}, timestamp=${timestamp}, signature=${mac}`;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function unwrapEnvelope<T = unknown>(obj: any): T;
|
package/dist/envelope.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function unwrapEnvelope(obj) {
|
|
2
|
+
if (obj === null || typeof obj !== "object")
|
|
3
|
+
return obj;
|
|
4
|
+
if (!Object.prototype.hasOwnProperty.call(obj, "data"))
|
|
5
|
+
return obj;
|
|
6
|
+
const data = obj.data;
|
|
7
|
+
const request = obj.request ?? {};
|
|
8
|
+
const requestId = request.id ?? obj.request_id;
|
|
9
|
+
const timestamp = obj.timestamp;
|
|
10
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
11
|
+
const out = { ...data };
|
|
12
|
+
if (requestId && out.request_id === undefined)
|
|
13
|
+
out.request_id = requestId;
|
|
14
|
+
if (timestamp !== undefined && out.timestamp === undefined)
|
|
15
|
+
out.timestamp = timestamp;
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
return (data ?? obj);
|
|
19
|
+
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare class MuxiError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly status: number;
|
|
4
|
+
readonly data?: unknown;
|
|
5
|
+
constructor(code: string, message: string, status: number, data?: unknown);
|
|
6
|
+
}
|
|
7
|
+
export declare class AuthenticationError extends MuxiError {
|
|
8
|
+
}
|
|
9
|
+
export declare class AuthorizationError extends MuxiError {
|
|
10
|
+
}
|
|
11
|
+
export declare class NotFoundError extends MuxiError {
|
|
12
|
+
}
|
|
13
|
+
export declare class ConflictError extends MuxiError {
|
|
14
|
+
}
|
|
15
|
+
export declare class ValidationError extends MuxiError {
|
|
16
|
+
}
|
|
17
|
+
export declare class RateLimitError extends MuxiError {
|
|
18
|
+
readonly retryAfter?: number;
|
|
19
|
+
constructor(message: string, status: number, retryAfter?: number, data?: unknown);
|
|
20
|
+
}
|
|
21
|
+
export declare class ServerError extends MuxiError {
|
|
22
|
+
}
|
|
23
|
+
export declare class ConnectionError extends MuxiError {
|
|
24
|
+
constructor(message: string);
|
|
25
|
+
}
|
|
26
|
+
export declare function mapError(status: number, code: string, message: string, details?: any, retryAfter?: number): MuxiError;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export class MuxiError extends Error {
|
|
2
|
+
constructor(code, message, status, data) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.code = code;
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.data = data;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export class AuthenticationError extends MuxiError {
|
|
10
|
+
}
|
|
11
|
+
export class AuthorizationError extends MuxiError {
|
|
12
|
+
}
|
|
13
|
+
export class NotFoundError extends MuxiError {
|
|
14
|
+
}
|
|
15
|
+
export class ConflictError extends MuxiError {
|
|
16
|
+
}
|
|
17
|
+
export class ValidationError extends MuxiError {
|
|
18
|
+
}
|
|
19
|
+
export class RateLimitError extends MuxiError {
|
|
20
|
+
constructor(message, status, retryAfter, data) {
|
|
21
|
+
super("RATE_LIMITED", message, status, data);
|
|
22
|
+
this.retryAfter = retryAfter;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class ServerError extends MuxiError {
|
|
26
|
+
}
|
|
27
|
+
export class ConnectionError extends MuxiError {
|
|
28
|
+
constructor(message) {
|
|
29
|
+
super("CONNECTION_ERROR", message, 0);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function mapError(status, code, message, details, retryAfter) {
|
|
33
|
+
if (status === 401)
|
|
34
|
+
return new AuthenticationError(code || "UNAUTHORIZED", message, status, details);
|
|
35
|
+
if (status === 403)
|
|
36
|
+
return new AuthorizationError(code || "FORBIDDEN", message, status, details);
|
|
37
|
+
if (status === 404)
|
|
38
|
+
return new NotFoundError(code || "NOT_FOUND", message, status, details);
|
|
39
|
+
if (status === 409)
|
|
40
|
+
return new ConflictError(code || "CONFLICT", message, status, details);
|
|
41
|
+
if (status === 422)
|
|
42
|
+
return new ValidationError(code || "VALIDATION_ERROR", message, status, details);
|
|
43
|
+
if (status === 429)
|
|
44
|
+
return new RateLimitError(message || "Too Many Requests", status, retryAfter, details);
|
|
45
|
+
if (status >= 500)
|
|
46
|
+
return new ServerError(code || "SERVER_ERROR", message, status, details);
|
|
47
|
+
return new MuxiError(code || "ERROR", message, status, details);
|
|
48
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export interface FormationClientOptions {
|
|
2
|
+
formationId: string;
|
|
3
|
+
clientKey?: string;
|
|
4
|
+
adminKey?: string;
|
|
5
|
+
serverUrl?: string;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
maxRetries?: number;
|
|
9
|
+
debug?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface RequestOptions {
|
|
12
|
+
params?: Record<string, any>;
|
|
13
|
+
body?: any;
|
|
14
|
+
userId?: string;
|
|
15
|
+
headers?: Record<string, string>;
|
|
16
|
+
}
|
|
17
|
+
export declare class FormationClient {
|
|
18
|
+
private readonly transport;
|
|
19
|
+
constructor(opts: FormationClientOptions);
|
|
20
|
+
health(): Promise<any>;
|
|
21
|
+
getStatus(): Promise<any>;
|
|
22
|
+
getConfig(): Promise<any>;
|
|
23
|
+
getFormationInfo(): Promise<any>;
|
|
24
|
+
getAgents(): Promise<any>;
|
|
25
|
+
getAgent(agentId: string): Promise<any>;
|
|
26
|
+
getMcpServers(): Promise<any>;
|
|
27
|
+
getMcpServer(id: string): Promise<any>;
|
|
28
|
+
getMcpTools(): Promise<any>;
|
|
29
|
+
getSecrets(): Promise<any>;
|
|
30
|
+
getSecret(key: string): Promise<any>;
|
|
31
|
+
setSecret(key: string, value: string): Promise<any>;
|
|
32
|
+
deleteSecret(key: string): Promise<any>;
|
|
33
|
+
chat(payload: Record<string, any>, userId?: string): Promise<any>;
|
|
34
|
+
chatStream(payload: Record<string, any>, userId?: string): AsyncGenerator<any, void, void>;
|
|
35
|
+
audioChat(payload: Record<string, any>, userId?: string): Promise<any>;
|
|
36
|
+
audioChatStream(payload: Record<string, any>, userId?: string): AsyncGenerator<any, void, void>;
|
|
37
|
+
getSessions(userId: string, limit?: number): Promise<any>;
|
|
38
|
+
getSession(sessionId: string, userId: string): Promise<any>;
|
|
39
|
+
getSessionMessages(sessionId: string, userId: string): Promise<any>;
|
|
40
|
+
restoreSession(sessionId: string, userId: string, messages: any[]): Promise<any>;
|
|
41
|
+
getRequests(userId: string): Promise<any>;
|
|
42
|
+
getRequestStatus(requestId: string, userId: string): Promise<any>;
|
|
43
|
+
cancelRequest(requestId: string, userId: string): Promise<any>;
|
|
44
|
+
getMemoryConfig(): Promise<any>;
|
|
45
|
+
getMemories(userId: string, limit?: number): Promise<any>;
|
|
46
|
+
addMemory(userId: string, type: string, detail: string): Promise<any>;
|
|
47
|
+
deleteMemory(userId: string, memoryId: string): Promise<any>;
|
|
48
|
+
getUserBuffer(userId: string): Promise<any>;
|
|
49
|
+
clearUserBuffer(userId: string): Promise<any>;
|
|
50
|
+
clearSessionBuffer(userId: string, sessionId: string): Promise<any>;
|
|
51
|
+
clearAllBuffers(): Promise<any>;
|
|
52
|
+
getMemoryBuffers(): Promise<any>;
|
|
53
|
+
getBufferStats(): Promise<any>;
|
|
54
|
+
getSchedulerConfig(): Promise<any>;
|
|
55
|
+
getSchedulerJobs(userId: string): Promise<any>;
|
|
56
|
+
getSchedulerJob(jobId: string): Promise<any>;
|
|
57
|
+
createSchedulerJob(jobType: string, schedule: string, message: string, userId: string): Promise<any>;
|
|
58
|
+
deleteSchedulerJob(jobId: string): Promise<any>;
|
|
59
|
+
getAsyncConfig(): Promise<any>;
|
|
60
|
+
getAsyncJobs(): Promise<any>;
|
|
61
|
+
getAsyncJob(jobId: string): Promise<any>;
|
|
62
|
+
cancelAsyncJob(jobId: string): Promise<any>;
|
|
63
|
+
getA2AConfig(): Promise<any>;
|
|
64
|
+
getLoggingConfig(): Promise<any>;
|
|
65
|
+
getLoggingDestinations(): Promise<any>;
|
|
66
|
+
listCredentialServices(): Promise<any>;
|
|
67
|
+
listCredentials(userId: string): Promise<any>;
|
|
68
|
+
getCredential(credentialId: string, userId: string): Promise<any>;
|
|
69
|
+
createCredential(userId: string, payload: Record<string, any>): Promise<any>;
|
|
70
|
+
deleteCredential(credentialId: string, userId: string): Promise<any>;
|
|
71
|
+
getUserIdentifiers(): Promise<any>;
|
|
72
|
+
getUserIdentifiersForUser(userId: string): Promise<any>;
|
|
73
|
+
linkUserIdentifier(muxiUserId: string, identifiers: any[]): Promise<any>;
|
|
74
|
+
unlinkUserIdentifier(identifier: string): Promise<any>;
|
|
75
|
+
getOverlordConfig(): Promise<any>;
|
|
76
|
+
getOverlordPersona(): Promise<any>;
|
|
77
|
+
getLlmSettings(): Promise<any>;
|
|
78
|
+
getTriggers(): Promise<any>;
|
|
79
|
+
getTrigger(name: string): Promise<any>;
|
|
80
|
+
fireTrigger(name: string, data: any, asyncMode?: boolean, userId?: string): Promise<any>;
|
|
81
|
+
getSops(): Promise<any>;
|
|
82
|
+
getSop(name: string): Promise<any>;
|
|
83
|
+
getAuditLog(): Promise<any>;
|
|
84
|
+
clearAuditLog(): Promise<any>;
|
|
85
|
+
streamEvents(userId: string): AsyncGenerator<any, void, void>;
|
|
86
|
+
streamRequest(userId: string, sessionId: string, requestId: string): AsyncGenerator<any, void, void>;
|
|
87
|
+
streamLogs(filters?: Record<string, any>): AsyncGenerator<any, void, void>;
|
|
88
|
+
resolveUser(identifier: string, createUser?: boolean): Promise<any>;
|
|
89
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { unwrapEnvelope } from "./envelope.js";
|
|
2
|
+
import { ConnectionError, MuxiError, mapError } from "./errors.js";
|
|
3
|
+
import { version } from "./version.js";
|
|
4
|
+
import { randomUUID } from "crypto";
|
|
5
|
+
const RETRY_STATUS = new Set([429, 500, 502, 503, 504]);
|
|
6
|
+
function computeBaseUrl(opts) {
|
|
7
|
+
if (opts.baseUrl)
|
|
8
|
+
return opts.baseUrl.replace(/\/$/, "");
|
|
9
|
+
if (!opts.serverUrl)
|
|
10
|
+
throw new Error("serverUrl or baseUrl is required");
|
|
11
|
+
return `${opts.serverUrl.replace(/\/$/, "")}/api/${opts.formationId}/v1`;
|
|
12
|
+
}
|
|
13
|
+
function buildUrl(baseUrl, path, params) {
|
|
14
|
+
const rel = path.startsWith("/") ? path : `/${path}`;
|
|
15
|
+
const search = new URLSearchParams();
|
|
16
|
+
Object.entries(params || {}).forEach(([k, v]) => {
|
|
17
|
+
if (v === undefined || v === null)
|
|
18
|
+
return;
|
|
19
|
+
search.set(k, String(v));
|
|
20
|
+
});
|
|
21
|
+
const query = search.toString();
|
|
22
|
+
const fullPath = query ? `${rel}?${query}` : rel;
|
|
23
|
+
return { url: `${baseUrl}${fullPath}`, fullPath };
|
|
24
|
+
}
|
|
25
|
+
async function parseJson(resp) {
|
|
26
|
+
const text = await resp.text();
|
|
27
|
+
if (!text)
|
|
28
|
+
return undefined;
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(text);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return text;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
class FormationTransport {
|
|
37
|
+
constructor(baseUrl, adminKey, clientKey, timeoutMs, maxRetries, debug) {
|
|
38
|
+
this.baseUrl = baseUrl;
|
|
39
|
+
this.adminKey = adminKey?.trim();
|
|
40
|
+
this.clientKey = clientKey?.trim();
|
|
41
|
+
this.timeoutMs = timeoutMs ?? 30_000;
|
|
42
|
+
this.maxRetries = maxRetries ?? 0;
|
|
43
|
+
this.debug = !!debug;
|
|
44
|
+
}
|
|
45
|
+
headers(useAdmin, userId, extra) {
|
|
46
|
+
const headers = {
|
|
47
|
+
"X-Muxi-SDK": `typescript/${version}`,
|
|
48
|
+
"X-Muxi-Client": `node-${process.version}`,
|
|
49
|
+
"X-Muxi-Idempotency-Key": randomUUID(),
|
|
50
|
+
};
|
|
51
|
+
if (useAdmin) {
|
|
52
|
+
if (!this.adminKey)
|
|
53
|
+
throw new Error("admin key required");
|
|
54
|
+
headers["X-MUXI-ADMIN-KEY"] = this.adminKey;
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
if (!this.clientKey)
|
|
58
|
+
throw new Error("client key required");
|
|
59
|
+
headers["X-MUXI-CLIENT-KEY"] = this.clientKey;
|
|
60
|
+
}
|
|
61
|
+
if (userId)
|
|
62
|
+
headers["X-Muxi-User-ID"] = userId;
|
|
63
|
+
if (extra)
|
|
64
|
+
Object.assign(headers, extra);
|
|
65
|
+
return headers;
|
|
66
|
+
}
|
|
67
|
+
async requestJson(method, path, opts) {
|
|
68
|
+
const { url, fullPath } = buildUrl(this.baseUrl, path, opts.params);
|
|
69
|
+
const headers = this.headers(opts.useAdmin, opts.userId, {
|
|
70
|
+
...(opts.body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
71
|
+
...(opts.headers || {}),
|
|
72
|
+
});
|
|
73
|
+
const body = opts.body === undefined ? undefined : JSON.stringify(opts.body);
|
|
74
|
+
let attempt = 0;
|
|
75
|
+
let backoff = 500;
|
|
76
|
+
while (true) {
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
79
|
+
try {
|
|
80
|
+
const resp = await fetch(url, { method, headers, body, signal: controller.signal });
|
|
81
|
+
clearTimeout(timeout);
|
|
82
|
+
if (this.debug)
|
|
83
|
+
console.debug(`${method} ${fullPath} -> ${resp.status}`);
|
|
84
|
+
if (resp.status >= 400) {
|
|
85
|
+
const payload = await parseJson(resp);
|
|
86
|
+
const code = payload?.code || payload?.error || "ERROR";
|
|
87
|
+
const message = payload?.message || resp.statusText;
|
|
88
|
+
const retryAfter = Number(resp.headers.get("Retry-After") || 0);
|
|
89
|
+
if (RETRY_STATUS.has(resp.status) && attempt < this.maxRetries) {
|
|
90
|
+
const sleepFor = Math.min(backoff, 30_000);
|
|
91
|
+
await new Promise((r) => setTimeout(r, sleepFor));
|
|
92
|
+
backoff *= 2;
|
|
93
|
+
attempt += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
throw mapError(resp.status, code, message, payload, retryAfter);
|
|
97
|
+
}
|
|
98
|
+
const data = await parseJson(resp);
|
|
99
|
+
return unwrapEnvelope(data);
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
clearTimeout(timeout);
|
|
103
|
+
if (err instanceof MuxiError)
|
|
104
|
+
throw err;
|
|
105
|
+
if (attempt < this.maxRetries) {
|
|
106
|
+
const sleepFor = Math.min(backoff, 30_000);
|
|
107
|
+
await new Promise((r) => setTimeout(r, sleepFor));
|
|
108
|
+
backoff *= 2;
|
|
109
|
+
attempt += 1;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
throw new ConnectionError(err?.message || String(err));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async *streamSse(method, path, opts) {
|
|
117
|
+
const { url, fullPath } = buildUrl(this.baseUrl, path, opts.params);
|
|
118
|
+
const headers = this.headers(opts.useAdmin, opts.userId, {
|
|
119
|
+
Accept: "text/event-stream",
|
|
120
|
+
...(opts.body !== undefined ? { "Content-Type": "application/json" } : {}),
|
|
121
|
+
...(opts.headers || {}),
|
|
122
|
+
});
|
|
123
|
+
const body = opts.body === undefined ? undefined : JSON.stringify(opts.body);
|
|
124
|
+
const resp = await fetch(url, { method, headers, body });
|
|
125
|
+
if (!resp.ok || !resp.body) {
|
|
126
|
+
const payload = await parseJson(resp);
|
|
127
|
+
throw mapError(resp.status, payload?.code || "STREAM_ERROR", payload?.message || resp.statusText, payload);
|
|
128
|
+
}
|
|
129
|
+
if (this.debug)
|
|
130
|
+
console.debug(`${method} ${fullPath} -> stream ${resp.status}`);
|
|
131
|
+
const reader = resp.body.getReader();
|
|
132
|
+
const decoder = new TextDecoder();
|
|
133
|
+
let buffer = "";
|
|
134
|
+
while (true) {
|
|
135
|
+
const { done, value } = await reader.read();
|
|
136
|
+
if (done)
|
|
137
|
+
break;
|
|
138
|
+
buffer += decoder.decode(value, { stream: true });
|
|
139
|
+
let idx;
|
|
140
|
+
while ((idx = buffer.indexOf("\n")) >= 0) {
|
|
141
|
+
const line = buffer.slice(0, idx).trimEnd();
|
|
142
|
+
buffer = buffer.slice(idx + 1);
|
|
143
|
+
if (!line)
|
|
144
|
+
continue;
|
|
145
|
+
if (line.startsWith("data:")) {
|
|
146
|
+
const payload = line.slice(5).trim();
|
|
147
|
+
if (!payload)
|
|
148
|
+
continue;
|
|
149
|
+
try {
|
|
150
|
+
yield JSON.parse(payload);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
yield payload;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if (buffer.trim()) {
|
|
159
|
+
const payload = buffer.trim();
|
|
160
|
+
try {
|
|
161
|
+
yield JSON.parse(payload);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
yield payload;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
export class FormationClient {
|
|
170
|
+
constructor(opts) {
|
|
171
|
+
const baseUrl = computeBaseUrl(opts);
|
|
172
|
+
this.transport = new FormationTransport(baseUrl, opts.adminKey, opts.clientKey, opts.timeoutMs, opts.maxRetries, opts.debug);
|
|
173
|
+
}
|
|
174
|
+
// Health / status / config
|
|
175
|
+
health() { return this.transport.requestJson("GET", "/health", { useAdmin: false }); }
|
|
176
|
+
getStatus() { return this.transport.requestJson("GET", "/status", { useAdmin: true }); }
|
|
177
|
+
getConfig() { return this.transport.requestJson("GET", "/config", { useAdmin: true }); }
|
|
178
|
+
getFormationInfo() { return this.transport.requestJson("GET", "/formation", { useAdmin: true }); }
|
|
179
|
+
// Agents / MCP
|
|
180
|
+
getAgents() { return this.transport.requestJson("GET", "/agents", { useAdmin: true }); }
|
|
181
|
+
getAgent(agentId) { return this.transport.requestJson("GET", `/agents/${agentId}`, { useAdmin: true }); }
|
|
182
|
+
getMcpServers() { return this.transport.requestJson("GET", "/mcp/servers", { useAdmin: true }); }
|
|
183
|
+
getMcpServer(id) { return this.transport.requestJson("GET", `/mcp/servers/${id}`, { useAdmin: true }); }
|
|
184
|
+
getMcpTools() { return this.transport.requestJson("GET", "/mcp/tools", { useAdmin: true }); }
|
|
185
|
+
// Secrets
|
|
186
|
+
getSecrets() { return this.transport.requestJson("GET", "/secrets", { useAdmin: true }); }
|
|
187
|
+
getSecret(key) { return this.transport.requestJson("GET", `/secrets/${key}`, { useAdmin: true }); }
|
|
188
|
+
setSecret(key, value) { return this.transport.requestJson("POST", `/secrets/${key}`, { useAdmin: true, body: { value } }); }
|
|
189
|
+
deleteSecret(key) { return this.transport.requestJson("DELETE", `/secrets/${key}`, { useAdmin: true }); }
|
|
190
|
+
// Chat
|
|
191
|
+
chat(payload, userId = "") { return this.transport.requestJson("POST", "/chat", { useAdmin: false, body: payload, userId }); }
|
|
192
|
+
chatStream(payload, userId = "") {
|
|
193
|
+
return this.transport.streamSse("POST", "/chat", { useAdmin: false, body: { ...payload, stream: true }, userId });
|
|
194
|
+
}
|
|
195
|
+
audioChat(payload, userId = "") { return this.transport.requestJson("POST", "/audiochat", { useAdmin: false, body: payload, userId }); }
|
|
196
|
+
audioChatStream(payload, userId = "") {
|
|
197
|
+
return this.transport.streamSse("POST", "/audiochat", { useAdmin: false, body: { ...payload, stream: true }, userId });
|
|
198
|
+
}
|
|
199
|
+
// Sessions / requests
|
|
200
|
+
getSessions(userId, limit) { return this.transport.requestJson("GET", "/sessions", { useAdmin: false, params: { user_id: userId, limit }, userId }); }
|
|
201
|
+
getSession(sessionId, userId) { return this.transport.requestJson("GET", `/sessions/${sessionId}`, { useAdmin: false, userId }); }
|
|
202
|
+
getSessionMessages(sessionId, userId) { return this.transport.requestJson("GET", `/sessions/${sessionId}/messages`, { useAdmin: false, userId }); }
|
|
203
|
+
restoreSession(sessionId, userId, messages) { return this.transport.requestJson("POST", `/sessions/${sessionId}/restore`, { useAdmin: false, userId, body: { messages } }); }
|
|
204
|
+
getRequests(userId) { return this.transport.requestJson("GET", "/requests", { useAdmin: false, userId }); }
|
|
205
|
+
getRequestStatus(requestId, userId) { return this.transport.requestJson("GET", `/requests/${requestId}`, { useAdmin: false, userId }); }
|
|
206
|
+
cancelRequest(requestId, userId) { return this.transport.requestJson("DELETE", `/requests/${requestId}`, { useAdmin: false, userId }); }
|
|
207
|
+
// Memory
|
|
208
|
+
getMemoryConfig() { return this.transport.requestJson("GET", "/memory", { useAdmin: true }); }
|
|
209
|
+
getMemories(userId, limit) { return this.transport.requestJson("GET", "/memory/user", { useAdmin: false, params: { user_id: userId, limit } }); }
|
|
210
|
+
addMemory(userId, type, detail) { return this.transport.requestJson("POST", "/memory", { useAdmin: false, body: { user_id: userId, type, detail } }); }
|
|
211
|
+
deleteMemory(userId, memoryId) { return this.transport.requestJson("DELETE", `/memory/${memoryId}`, { useAdmin: false, params: { user_id: userId } }); }
|
|
212
|
+
getUserBuffer(userId) { return this.transport.requestJson("GET", `/memory/buffer/${userId}`, { useAdmin: false }); }
|
|
213
|
+
clearUserBuffer(userId) { return this.transport.requestJson("DELETE", `/memory/buffer/${userId}`, { useAdmin: false }); }
|
|
214
|
+
clearSessionBuffer(userId, sessionId) { return this.transport.requestJson("DELETE", `/memory/buffer/${userId}/${sessionId}`, { useAdmin: false }); }
|
|
215
|
+
clearAllBuffers() { return this.transport.requestJson("DELETE", "/memory/buffer", { useAdmin: true }); }
|
|
216
|
+
getMemoryBuffers() { return this.transport.requestJson("GET", "/memory/buffers", { useAdmin: true }); }
|
|
217
|
+
getBufferStats() { return this.transport.requestJson("GET", "/memory/stats", { useAdmin: true }); }
|
|
218
|
+
// Scheduler
|
|
219
|
+
getSchedulerConfig() { return this.transport.requestJson("GET", "/scheduler/config", { useAdmin: true }); }
|
|
220
|
+
getSchedulerJobs(userId) { return this.transport.requestJson("GET", "/scheduler/jobs", { useAdmin: true, params: { user_id: userId } }); }
|
|
221
|
+
getSchedulerJob(jobId) { return this.transport.requestJson("GET", `/scheduler/jobs/${jobId}`, { useAdmin: true }); }
|
|
222
|
+
createSchedulerJob(jobType, schedule, message, userId) {
|
|
223
|
+
return this.transport.requestJson("POST", "/scheduler/jobs", { useAdmin: true, body: { type: jobType, schedule, message, user_id: userId } });
|
|
224
|
+
}
|
|
225
|
+
deleteSchedulerJob(jobId) { return this.transport.requestJson("DELETE", `/scheduler/jobs/${jobId}`, { useAdmin: true }); }
|
|
226
|
+
// Async / logging / a2a
|
|
227
|
+
getAsyncConfig() { return this.transport.requestJson("GET", "/async", { useAdmin: true }); }
|
|
228
|
+
getAsyncJobs() { return this.transport.requestJson("GET", "/async/jobs", { useAdmin: true }); }
|
|
229
|
+
getAsyncJob(jobId) { return this.transport.requestJson("GET", `/async/jobs/${jobId}`, { useAdmin: true }); }
|
|
230
|
+
cancelAsyncJob(jobId) { return this.transport.requestJson("DELETE", `/async/jobs/${jobId}`, { useAdmin: true }); }
|
|
231
|
+
getA2AConfig() { return this.transport.requestJson("GET", "/a2a", { useAdmin: true }); }
|
|
232
|
+
getLoggingConfig() { return this.transport.requestJson("GET", "/logging", { useAdmin: true }); }
|
|
233
|
+
getLoggingDestinations() { return this.transport.requestJson("GET", "/logging/destinations", { useAdmin: true }); }
|
|
234
|
+
// Credentials / identifiers
|
|
235
|
+
listCredentialServices() { return this.transport.requestJson("GET", "/credentials/services", { useAdmin: true }); }
|
|
236
|
+
listCredentials(userId) { return this.transport.requestJson("GET", "/credentials", { useAdmin: false, userId }); }
|
|
237
|
+
getCredential(credentialId, userId) { return this.transport.requestJson("GET", `/credentials/${credentialId}`, { useAdmin: false, userId }); }
|
|
238
|
+
createCredential(userId, payload) { return this.transport.requestJson("POST", "/credentials", { useAdmin: false, userId, body: payload }); }
|
|
239
|
+
deleteCredential(credentialId, userId) { return this.transport.requestJson("DELETE", `/credentials/${credentialId}`, { useAdmin: false, userId }); }
|
|
240
|
+
getUserIdentifiers() { return this.transport.requestJson("GET", "/users/identifiers", { useAdmin: true }); }
|
|
241
|
+
getUserIdentifiersForUser(userId) { return this.transport.requestJson("GET", `/users/${userId}/identifiers`, { useAdmin: true }); }
|
|
242
|
+
linkUserIdentifier(muxiUserId, identifiers) { return this.transport.requestJson("POST", "/users/identifiers", { useAdmin: true, body: { muxi_user_id: muxiUserId, identifiers } }); }
|
|
243
|
+
unlinkUserIdentifier(identifier) { return this.transport.requestJson("DELETE", `/users/identifiers/${identifier}`, { useAdmin: true }); }
|
|
244
|
+
// Overlord / LLM
|
|
245
|
+
getOverlordConfig() { return this.transport.requestJson("GET", "/overlord", { useAdmin: true }); }
|
|
246
|
+
getOverlordPersona() { return this.transport.requestJson("GET", "/overlord/persona", { useAdmin: true }); }
|
|
247
|
+
getLlmSettings() { return this.transport.requestJson("GET", "/llm/settings", { useAdmin: true }); }
|
|
248
|
+
// Triggers / SOP / Audit
|
|
249
|
+
getTriggers() { return this.transport.requestJson("GET", "/triggers", { useAdmin: false }); }
|
|
250
|
+
getTrigger(name) { return this.transport.requestJson("GET", `/triggers/${name}`, { useAdmin: false }); }
|
|
251
|
+
fireTrigger(name, data, asyncMode = false, userId = "") { return this.transport.requestJson("POST", `/triggers/${name}`, { useAdmin: false, userId, params: { async: String(asyncMode).toLowerCase() }, body: data }); }
|
|
252
|
+
getSops() { return this.transport.requestJson("GET", "/sops", { useAdmin: false }); }
|
|
253
|
+
getSop(name) { return this.transport.requestJson("GET", `/sops/${name}`, { useAdmin: false }); }
|
|
254
|
+
getAuditLog() { return this.transport.requestJson("GET", "/audit", { useAdmin: true }); }
|
|
255
|
+
clearAuditLog() { return this.transport.requestJson("DELETE", "/audit", { useAdmin: true, params: { confirm: "clear-audit-log" } }); }
|
|
256
|
+
// Events / logs streaming
|
|
257
|
+
streamEvents(userId) { return this.transport.streamSse("GET", `/events/${userId}`, { useAdmin: false }); }
|
|
258
|
+
streamRequest(userId, sessionId, requestId) { return this.transport.streamSse("GET", `/requests/${requestId}/stream`, { useAdmin: false, params: { user_id: userId, session_id: sessionId } }); }
|
|
259
|
+
streamLogs(filters) { return this.transport.streamSse("POST", "/logs/stream", { useAdmin: true, body: filters || {} }); }
|
|
260
|
+
// Resolve user
|
|
261
|
+
resolveUser(identifier, createUser = false) { return this.transport.requestJson("GET", "/users/resolve", { useAdmin: false, params: { identifier, create_user: String(createUser).toLowerCase() } }); }
|
|
262
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { RequestOptions } from "./transport.js";
|
|
2
|
+
export interface ServerClientOptions {
|
|
3
|
+
url: string;
|
|
4
|
+
keyId: string;
|
|
5
|
+
secretKey: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
maxRetries?: number;
|
|
8
|
+
debug?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare class ServerClient {
|
|
11
|
+
private readonly transport;
|
|
12
|
+
constructor(opts: ServerClientOptions);
|
|
13
|
+
ping(): Promise<any>;
|
|
14
|
+
health(): Promise<any>;
|
|
15
|
+
status(): Promise<any>;
|
|
16
|
+
listFormations(): Promise<any>;
|
|
17
|
+
getFormation(id: string): Promise<any>;
|
|
18
|
+
stopFormation(id: string): Promise<any>;
|
|
19
|
+
startFormation(id: string): Promise<any>;
|
|
20
|
+
restartFormation(id: string): Promise<any>;
|
|
21
|
+
rollbackFormation(id: string): Promise<any>;
|
|
22
|
+
deleteFormation(id: string): Promise<any>;
|
|
23
|
+
cancelUpdate(id: string): Promise<any>;
|
|
24
|
+
deployFormation(id: string, payload: Record<string, any>): Promise<any>;
|
|
25
|
+
updateFormation(id: string, payload: Record<string, any>): Promise<any>;
|
|
26
|
+
getFormationLogs(id: string, limit?: number): Promise<any>;
|
|
27
|
+
getServerLogs(limit?: number): Promise<any>;
|
|
28
|
+
deployFormationStream(id: string, payload: Record<string, any>): AsyncGenerator<any, void, unknown>;
|
|
29
|
+
updateFormationStream(id: string, payload: Record<string, any>): AsyncGenerator<any, void, unknown>;
|
|
30
|
+
startFormationStream(id: string): AsyncGenerator<any, void, unknown>;
|
|
31
|
+
restartFormationStream(id: string): AsyncGenerator<any, void, unknown>;
|
|
32
|
+
rollbackFormationStream(id: string): AsyncGenerator<any, void, unknown>;
|
|
33
|
+
streamFormationLogs(id: string): AsyncGenerator<any, void, unknown>;
|
|
34
|
+
_rpcGet<T = any>(path: string, options?: RequestOptions): Promise<T>;
|
|
35
|
+
_rpcPost<T = any>(path: string, body: Record<string, any>): Promise<T>;
|
|
36
|
+
_rpcDelete<T = any>(path: string): Promise<T>;
|
|
37
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Transport } from "./transport.js";
|
|
2
|
+
async function* parseSseLines(lines) {
|
|
3
|
+
for await (const line of lines) {
|
|
4
|
+
const trimmed = line.trim();
|
|
5
|
+
if (!trimmed)
|
|
6
|
+
continue;
|
|
7
|
+
if (!trimmed.startsWith("data:"))
|
|
8
|
+
continue;
|
|
9
|
+
const payload = trimmed.slice(5).trim();
|
|
10
|
+
if (!payload)
|
|
11
|
+
continue;
|
|
12
|
+
try {
|
|
13
|
+
yield JSON.parse(payload);
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
yield payload;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class ServerClient {
|
|
21
|
+
constructor(opts) {
|
|
22
|
+
const topts = {
|
|
23
|
+
baseUrl: opts.url.replace(/\/$/, ""),
|
|
24
|
+
keyId: opts.keyId,
|
|
25
|
+
secretKey: opts.secretKey,
|
|
26
|
+
timeoutMs: opts.timeoutMs,
|
|
27
|
+
maxRetries: opts.maxRetries,
|
|
28
|
+
debug: opts.debug,
|
|
29
|
+
};
|
|
30
|
+
this.transport = new Transport(topts);
|
|
31
|
+
}
|
|
32
|
+
// Unauthenticated
|
|
33
|
+
ping() { return this.transport.requestJson("GET", "/ping"); }
|
|
34
|
+
health() { return this.transport.requestJson("GET", "/health"); }
|
|
35
|
+
// Authenticated
|
|
36
|
+
status() { return this._rpcGet("/rpc/server/status"); }
|
|
37
|
+
listFormations() { return this._rpcGet("/rpc/formations"); }
|
|
38
|
+
getFormation(id) { return this._rpcGet(`/rpc/formations/${id}`); }
|
|
39
|
+
stopFormation(id) { return this._rpcPost(`/rpc/formations/${id}/stop`, {}); }
|
|
40
|
+
startFormation(id) { return this._rpcPost(`/rpc/formations/${id}/start`, {}); }
|
|
41
|
+
restartFormation(id) { return this._rpcPost(`/rpc/formations/${id}/restart`, {}); }
|
|
42
|
+
rollbackFormation(id) { return this._rpcPost(`/rpc/formations/${id}/rollback`, {}); }
|
|
43
|
+
deleteFormation(id) { return this._rpcDelete(`/rpc/formations/${id}`); }
|
|
44
|
+
cancelUpdate(id) { return this._rpcPost(`/rpc/formations/${id}/cancel-update`, {}); }
|
|
45
|
+
deployFormation(id, payload) { return this._rpcPost(`/rpc/formations/${id}/deploy`, payload); }
|
|
46
|
+
updateFormation(id, payload) { return this._rpcPost(`/rpc/formations/${id}/update`, payload); }
|
|
47
|
+
getFormationLogs(id, limit) { return this._rpcGet(`/rpc/formations/${id}/logs`, { params: limit !== undefined ? { limit } : undefined }); }
|
|
48
|
+
getServerLogs(limit) { return this._rpcGet(`/rpc/server/logs`, { params: limit !== undefined ? { limit } : undefined }); }
|
|
49
|
+
// Streaming
|
|
50
|
+
deployFormationStream(id, payload) { return parseSseLines(this.transport.streamLines("POST", `/rpc/formations/${id}/deploy/stream`, { body: payload })); }
|
|
51
|
+
updateFormationStream(id, payload) { return parseSseLines(this.transport.streamLines("POST", `/rpc/formations/${id}/update/stream`, { body: payload })); }
|
|
52
|
+
startFormationStream(id) { return parseSseLines(this.transport.streamLines("POST", `/rpc/formations/${id}/start/stream`, { body: {} })); }
|
|
53
|
+
restartFormationStream(id) { return parseSseLines(this.transport.streamLines("POST", `/rpc/formations/${id}/restart/stream`, { body: {} })); }
|
|
54
|
+
rollbackFormationStream(id) { return parseSseLines(this.transport.streamLines("POST", `/rpc/formations/${id}/rollback/stream`, { body: {} })); }
|
|
55
|
+
streamFormationLogs(id) { return parseSseLines(this.transport.streamLines("GET", `/rpc/formations/${id}/logs/stream`)); }
|
|
56
|
+
// Generic RPC helpers
|
|
57
|
+
_rpcGet(path, options = {}) { return this.transport.requestJson("GET", path, options); }
|
|
58
|
+
_rpcPost(path, body) { return this.transport.requestJson("POST", path, { body }); }
|
|
59
|
+
_rpcDelete(path) { return this.transport.requestJson("DELETE", path); }
|
|
60
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface TransportOptions {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
keyId?: string;
|
|
4
|
+
secretKey?: string;
|
|
5
|
+
timeoutMs?: number;
|
|
6
|
+
maxRetries?: number;
|
|
7
|
+
debug?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface RequestOptions {
|
|
10
|
+
params?: Record<string, any>;
|
|
11
|
+
body?: any;
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
authPath?: string;
|
|
14
|
+
stream?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare class Transport {
|
|
17
|
+
private readonly opts;
|
|
18
|
+
constructor(opts: TransportOptions);
|
|
19
|
+
requestJson<T = any>(method: string, path: string, options?: RequestOptions): Promise<T>;
|
|
20
|
+
streamLines(method: string, path: string, options?: RequestOptions): AsyncGenerator<string, void, void>;
|
|
21
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { buildAuthHeader } from "./auth.js";
|
|
2
|
+
import { unwrapEnvelope } from "./envelope.js";
|
|
3
|
+
import { ConnectionError, MuxiError, mapError } from "./errors.js";
|
|
4
|
+
import { version } from "./version.js";
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
const RETRY_STATUS = new Set([429, 500, 502, 503, 504]);
|
|
7
|
+
function buildUrl(baseUrl, path, params) {
|
|
8
|
+
const rel = path.startsWith("/") ? path : `/${path}`;
|
|
9
|
+
const search = new URLSearchParams();
|
|
10
|
+
Object.entries(params || {}).forEach(([k, v]) => {
|
|
11
|
+
if (v === undefined || v === null)
|
|
12
|
+
return;
|
|
13
|
+
search.set(k, String(v));
|
|
14
|
+
});
|
|
15
|
+
const query = search.toString();
|
|
16
|
+
const fullPath = query ? `${rel}?${query}` : rel;
|
|
17
|
+
return { url: `${baseUrl}${fullPath}`, fullPath };
|
|
18
|
+
}
|
|
19
|
+
function baseHeaders(opts, method, pathForAuth, extra) {
|
|
20
|
+
const headers = {
|
|
21
|
+
"Content-Type": "application/json",
|
|
22
|
+
"X-Muxi-SDK": `typescript/${version}`,
|
|
23
|
+
"X-Muxi-Client": `node-${process.version}`,
|
|
24
|
+
"X-Muxi-Idempotency-Key": randomUUID(),
|
|
25
|
+
};
|
|
26
|
+
if (opts.keyId && opts.secretKey) {
|
|
27
|
+
headers.Authorization = buildAuthHeader(opts.keyId.trim(), opts.secretKey.trim(), method, pathForAuth);
|
|
28
|
+
}
|
|
29
|
+
if (extra)
|
|
30
|
+
Object.assign(headers, extra);
|
|
31
|
+
return headers;
|
|
32
|
+
}
|
|
33
|
+
async function parseJson(resp) {
|
|
34
|
+
const text = await resp.text();
|
|
35
|
+
if (!text)
|
|
36
|
+
return undefined;
|
|
37
|
+
try {
|
|
38
|
+
return JSON.parse(text);
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return text;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export class Transport {
|
|
45
|
+
constructor(opts) {
|
|
46
|
+
this.opts = { ...opts, timeoutMs: opts.timeoutMs ?? 30_000, maxRetries: opts.maxRetries ?? 0 };
|
|
47
|
+
}
|
|
48
|
+
async requestJson(method, path, options = {}) {
|
|
49
|
+
const { url, fullPath } = buildUrl(this.opts.baseUrl, path, options.params);
|
|
50
|
+
const headers = baseHeaders(this.opts, method, options.authPath ?? fullPath, options.headers);
|
|
51
|
+
const body = options.body === undefined ? undefined : JSON.stringify(options.body);
|
|
52
|
+
let attempt = 0;
|
|
53
|
+
let backoff = 500;
|
|
54
|
+
while (true) {
|
|
55
|
+
const controller = new AbortController();
|
|
56
|
+
const timeout = setTimeout(() => controller.abort(), this.opts.timeoutMs);
|
|
57
|
+
try {
|
|
58
|
+
const resp = await fetch(url, {
|
|
59
|
+
method,
|
|
60
|
+
headers,
|
|
61
|
+
body,
|
|
62
|
+
signal: controller.signal,
|
|
63
|
+
});
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
if (this.opts.debug) {
|
|
66
|
+
console.debug(`${method} ${url} -> ${resp.status}`);
|
|
67
|
+
}
|
|
68
|
+
if (resp.status >= 400) {
|
|
69
|
+
const payload = await parseJson(resp);
|
|
70
|
+
const code = payload?.code || payload?.error || "ERROR";
|
|
71
|
+
const message = payload?.message || resp.statusText;
|
|
72
|
+
const retryAfter = Number(resp.headers.get("Retry-After") || 0);
|
|
73
|
+
if (RETRY_STATUS.has(resp.status) && attempt < (this.opts.maxRetries ?? 0)) {
|
|
74
|
+
const sleepFor = Math.min(backoff, 30_000);
|
|
75
|
+
await new Promise((r) => setTimeout(r, sleepFor));
|
|
76
|
+
backoff *= 2;
|
|
77
|
+
attempt += 1;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
throw mapError(resp.status, code, message, payload, retryAfter);
|
|
81
|
+
}
|
|
82
|
+
const data = await parseJson(resp);
|
|
83
|
+
return unwrapEnvelope(data);
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
clearTimeout(timeout);
|
|
87
|
+
if (err instanceof MuxiError)
|
|
88
|
+
throw err;
|
|
89
|
+
if (attempt < (this.opts.maxRetries ?? 0)) {
|
|
90
|
+
const sleepFor = Math.min(backoff, 30_000);
|
|
91
|
+
await new Promise((r) => setTimeout(r, sleepFor));
|
|
92
|
+
backoff *= 2;
|
|
93
|
+
attempt += 1;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
throw new ConnectionError(err?.message || String(err));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async *streamLines(method, path, options = {}) {
|
|
101
|
+
const { url, fullPath } = buildUrl(this.opts.baseUrl, path, options.params);
|
|
102
|
+
const headers = baseHeaders(this.opts, method, options.authPath ?? fullPath, {
|
|
103
|
+
Accept: "text/event-stream",
|
|
104
|
+
...(options.headers || {}),
|
|
105
|
+
});
|
|
106
|
+
const body = options.body === undefined ? undefined : JSON.stringify(options.body);
|
|
107
|
+
const resp = await fetch(url, {
|
|
108
|
+
method,
|
|
109
|
+
headers,
|
|
110
|
+
body,
|
|
111
|
+
signal: undefined,
|
|
112
|
+
});
|
|
113
|
+
if (!resp.ok || !resp.body) {
|
|
114
|
+
const payload = await parseJson(resp);
|
|
115
|
+
throw new MuxiError(payload?.code || "STREAM_ERROR", payload?.message || resp.statusText, resp.status, payload);
|
|
116
|
+
}
|
|
117
|
+
const reader = resp.body.getReader();
|
|
118
|
+
const decoder = new TextDecoder();
|
|
119
|
+
let buffer = "";
|
|
120
|
+
while (true) {
|
|
121
|
+
const { done, value } = await reader.read();
|
|
122
|
+
if (done)
|
|
123
|
+
break;
|
|
124
|
+
buffer += decoder.decode(value, { stream: true });
|
|
125
|
+
let idx;
|
|
126
|
+
while ((idx = buffer.indexOf("\n")) >= 0) {
|
|
127
|
+
const line = buffer.slice(0, idx).trimEnd();
|
|
128
|
+
buffer = buffer.slice(idx + 1);
|
|
129
|
+
if (!line)
|
|
130
|
+
continue;
|
|
131
|
+
yield line;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
if (buffer.trim()) {
|
|
135
|
+
yield buffer.trim();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const version = "0.0.0";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const version = "0.0.0";
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@muxi-ai/muxi-typescript",
|
|
3
|
+
"version": "0.20260107.0",
|
|
4
|
+
"description": "Official MUXI SDK for TypeScript",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
21
|
+
"build": "npm run clean && tsc -p tsconfig.json",
|
|
22
|
+
"test": "npm run build && npm run test:unit && npm run test:e2e",
|
|
23
|
+
"test:unit": "node --test --loader ts-node/esm tests/unit/*.test.ts",
|
|
24
|
+
"test:e2e": "node --test --loader ts-node/esm tests/e2e/*.test.ts"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public",
|
|
31
|
+
"provenance": true
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/muxi-ai/muxi-typescript.git"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/muxi-ai/muxi-typescript/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/muxi-ai/muxi-typescript#readme",
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^20.11.24",
|
|
43
|
+
"ts-node": "^10.9.2",
|
|
44
|
+
"typescript": "^5.3.3"
|
|
45
|
+
}
|
|
46
|
+
}
|