pi-freeflow 1.3.3 → 1.3.6
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 +110 -93
- package/package.json +2 -2
- package/src/catalog.ts +8 -5
- package/src/commands.ts +242 -92
- package/src/index.ts +47 -14
- package/src/models.ts +72 -8
- package/src/proxy.ts +31 -10
- package/src/relay-state.ts +125 -21
- package/src/types.ts +2 -1
package/src/models.ts
CHANGED
|
@@ -305,9 +305,57 @@ export const KILO_MODELS: ModelDef[] = [
|
|
|
305
305
|
];
|
|
306
306
|
|
|
307
307
|
/**
|
|
308
|
-
*
|
|
308
|
+
* Model ID Aliases — maps user-friendly / slash-free CLI IDs to canonical upstream model IDs.
|
|
309
309
|
*/
|
|
310
|
-
export const
|
|
310
|
+
export const MODEL_ALIASES: Record<string, string> = {
|
|
311
|
+
// Kilo Gateway slash-free & colon-free CLI aliases
|
|
312
|
+
"dots-3-note-preview": "dots-studio/dots-3-note-preview:free",
|
|
313
|
+
"dots-3-note-preview:free": "dots-studio/dots-3-note-preview:free",
|
|
314
|
+
"step-3.7-flash": "stepfun/step-3.7-flash:free",
|
|
315
|
+
"step-3.7-flash:free": "stepfun/step-3.7-flash:free",
|
|
316
|
+
"nemotron-3-nano-omni": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
|
|
317
|
+
"nemotron-3-nano-omni:free": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
|
|
318
|
+
"nemotron-3-ultra-550b": "nvidia/nemotron-3-ultra-550b-a55b:free",
|
|
319
|
+
"nemotron-3-ultra-550b:free": "nvidia/nemotron-3-ultra-550b-a55b:free",
|
|
320
|
+
"nemotron-3-super": "nvidia/nemotron-3-super-120b-a12b:free",
|
|
321
|
+
"nemotron-3-super:free": "nvidia/nemotron-3-super-120b-a12b:free",
|
|
322
|
+
"hy3:free": "tencent/hy3:free",
|
|
323
|
+
"north-mini-code": "cohere/north-mini-code:free",
|
|
324
|
+
"north-mini-code:free": "cohere/north-mini-code:free",
|
|
325
|
+
"laguna-s-2.1:free": "poolside/laguna-s-2.1:free",
|
|
326
|
+
"laguna-xs-2.1:free": "poolside/laguna-xs-2.1:free",
|
|
327
|
+
"lfm-2.5": "liquid/lfm-2.5-2.6b:free",
|
|
328
|
+
"lfm-2.5:free": "liquid/lfm-2.5-2.6b:free",
|
|
329
|
+
"content-safety": "nvidia/nemotron-3.5-content-safety:free",
|
|
330
|
+
"content-safety:free": "nvidia/nemotron-3.5-content-safety:free",
|
|
331
|
+
"kilo-auto": "kilo-auto/free",
|
|
332
|
+
"openrouter": "openrouter/free",
|
|
333
|
+
|
|
334
|
+
// OpenCode Zen aliases
|
|
335
|
+
"claude-sonnet-4.5-free": "muse-spark-1.2-contributor-free",
|
|
336
|
+
"claude-sonnet-4.5-contributor-free": "muse-spark-1.2-contributor-free",
|
|
337
|
+
"grok-code-fast-1-preview-f-free": "x-preview-f-free",
|
|
338
|
+
"minimax-m2.1-free": "laguna-s-2.1-free",
|
|
339
|
+
"qwen3-coder-480b-free": "hy3-free",
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Resolve any model alias to its canonical upstream model ID.
|
|
344
|
+
*/
|
|
345
|
+
export function resolveCanonicalModelId(id: string): string {
|
|
346
|
+
const clean = (id || "").trim();
|
|
347
|
+
return MODEL_ALIASES[clean] || clean;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Set of all KiloCode model IDs (including aliases) for fast lookup
|
|
352
|
+
*/
|
|
353
|
+
export const KILO_MODEL_IDS = new Set<string>([
|
|
354
|
+
...KILO_MODELS.map((m) => m.id),
|
|
355
|
+
...Object.entries(MODEL_ALIASES)
|
|
356
|
+
.filter(([_, target]) => KILO_MODELS.some((km) => km.id === target))
|
|
357
|
+
.map(([alias]) => alias),
|
|
358
|
+
]);
|
|
311
359
|
|
|
312
360
|
/**
|
|
313
361
|
* Combined list of all 23 static free models
|
|
@@ -317,22 +365,38 @@ export const ALL_MODELS: ModelDef[] = [...OPENCODE_MODELS, ...KILO_MODELS];
|
|
|
317
365
|
/**
|
|
318
366
|
* Map of model ID -> ModelDef
|
|
319
367
|
*/
|
|
320
|
-
export const MODEL_MAP = new Map<string, ModelDef>(
|
|
321
|
-
ALL_MODELS.map((m) => [m.id, m]),
|
|
322
|
-
)
|
|
323
|
-
|
|
368
|
+
export const MODEL_MAP = new Map<string, ModelDef>([
|
|
369
|
+
...ALL_MODELS.map((m): [string, ModelDef] => [m.id, m]),
|
|
370
|
+
...Object.entries(MODEL_ALIASES).map(([aliasId, canonicalId]): [string, ModelDef] => {
|
|
371
|
+
const base = ALL_MODELS.find((m) => m.id === canonicalId);
|
|
372
|
+
return [
|
|
373
|
+
aliasId,
|
|
374
|
+
base
|
|
375
|
+
? { ...base, id: aliasId }
|
|
376
|
+
: { id: aliasId, name: aliasId, reasoning: false, contextWindow: 200_000, maxTokens: 32_000, input: ["text"] },
|
|
377
|
+
];
|
|
378
|
+
}),
|
|
379
|
+
]);
|
|
324
380
|
/**
|
|
325
381
|
* Lookup a model definition by ID
|
|
326
382
|
*/
|
|
327
383
|
export function getModelDef(id: string): ModelDef | undefined {
|
|
328
|
-
return MODEL_MAP.get(id);
|
|
384
|
+
return MODEL_MAP.get(id) || MODEL_MAP.get(resolveCanonicalModelId(id));
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Get full list of registered models including CLI aliases
|
|
388
|
+
*/
|
|
389
|
+
export function getAllRegisteredModels(): ModelDef[] {
|
|
390
|
+
return Array.from(MODEL_MAP.values());
|
|
329
391
|
}
|
|
330
392
|
|
|
393
|
+
|
|
331
394
|
/**
|
|
332
395
|
* Check if a model ID belongs to KiloCode Gateway
|
|
333
396
|
*/
|
|
334
397
|
export function isKiloModel(id: string): boolean {
|
|
335
|
-
|
|
398
|
+
const canonical = resolveCanonicalModelId(id);
|
|
399
|
+
return KILO_MODEL_IDS.has(id) || KILO_MODEL_IDS.has(canonical);
|
|
336
400
|
}
|
|
337
401
|
|
|
338
402
|
/**
|
package/src/proxy.ts
CHANGED
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
opencodeHeaders,
|
|
24
24
|
} from "./config.ts";
|
|
25
25
|
import { isDebugEnabled, log } from "./logger.ts";
|
|
26
|
-
import { KILO_MODEL_IDS } from "./models.ts";
|
|
26
|
+
import { KILO_MODEL_IDS, resolveCanonicalModelId } from "./models.ts";
|
|
27
27
|
// normalize removed — host pi-ai already normalizes thinking/reasoning before proxy
|
|
28
28
|
import { checkRateLimit } from "./rate-limiter.ts";
|
|
29
29
|
import { relayFetch } from "./relay.ts";
|
|
@@ -87,6 +87,23 @@ export function sanitizeHeaders(
|
|
|
87
87
|
return sanitized;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Clamps reasoning_effort for upstream models with strict non-standard enums
|
|
92
|
+
* (e.g. OpenCode x-preview strictly requires 'low', 'high', or 'max' and rejects 'medium' with 400).
|
|
93
|
+
*/
|
|
94
|
+
function sanitizeReasoningForModel(bodyObj: Record<string, unknown>): void {
|
|
95
|
+
const model = String(bodyObj.model || "").toLowerCase();
|
|
96
|
+
if (model.includes("x-preview")) {
|
|
97
|
+
const effort = String(bodyObj.reasoning_effort || "").toLowerCase();
|
|
98
|
+
if (effort === "medium") {
|
|
99
|
+
bodyObj.reasoning_effort = "high";
|
|
100
|
+
} else if (effort === "minimal") {
|
|
101
|
+
bodyObj.reasoning_effort = "low";
|
|
102
|
+
} else if (!effort || effort === "off" || effort === "none") {
|
|
103
|
+
bodyObj.reasoning_effort = "low";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
90
107
|
/**
|
|
91
108
|
* Probe whether an existing pi-freeflow proxy daemon is running and responsive on a given port.
|
|
92
109
|
*/
|
|
@@ -198,11 +215,12 @@ export function startProxy(
|
|
|
198
215
|
|
|
199
216
|
try {
|
|
200
217
|
parsedBody = JSON.parse(bodyStr);
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
218
|
+
if (typeof parsedBody?.model === "string") {
|
|
219
|
+
const canonical = resolveCanonicalModelId(parsedBody.model);
|
|
220
|
+
parsedBody.model = canonical;
|
|
221
|
+
if (KILO_MODEL_IDS.has(canonical)) {
|
|
222
|
+
isKilo = true;
|
|
223
|
+
}
|
|
206
224
|
}
|
|
207
225
|
} catch {}
|
|
208
226
|
|
|
@@ -266,10 +284,11 @@ export function startProxy(
|
|
|
266
284
|
} else {
|
|
267
285
|
// OpenCode routing — relay when enabled, else direct upstream
|
|
268
286
|
const relayState = getActiveRelayState();
|
|
269
|
-
|
|
270
|
-
relayState.
|
|
271
|
-
|
|
272
|
-
|
|
287
|
+
const shouldUseRelay =
|
|
288
|
+
relayState.mode !== "off" &&
|
|
289
|
+
relayState.enabled !== false &&
|
|
290
|
+
Boolean(relayState.url || (relayState.relays && relayState.relays.length > 0));
|
|
291
|
+
if (shouldUseRelay) {
|
|
273
292
|
const fullUrl = `${UPSTREAM_OPENCODE}${req.url ?? "/"}`;
|
|
274
293
|
const activeHost = relayState.url
|
|
275
294
|
? new URL(relayState.url).host
|
|
@@ -279,6 +298,7 @@ export function startProxy(
|
|
|
279
298
|
try {
|
|
280
299
|
if (parsedBody) {
|
|
281
300
|
const relayBodyObj = structuredClone(parsedBody);
|
|
301
|
+
sanitizeReasoningForModel(relayBodyObj as Record<string, unknown>);
|
|
282
302
|
const relayBody = Buffer.from(JSON.stringify(relayBodyObj));
|
|
283
303
|
const response = await relayFetch(
|
|
284
304
|
fullUrl,
|
|
@@ -334,6 +354,7 @@ export function startProxy(
|
|
|
334
354
|
let directBody = Buffer.concat(bodyChunks);
|
|
335
355
|
if (parsedBody) {
|
|
336
356
|
const directBodyObj = structuredClone(parsedBody);
|
|
357
|
+
sanitizeReasoningForModel(directBodyObj as Record<string, unknown>);
|
|
337
358
|
directBody = Buffer.from(JSON.stringify(directBodyObj));
|
|
338
359
|
}
|
|
339
360
|
|
package/src/relay-state.ts
CHANGED
|
@@ -9,27 +9,35 @@ import fs from "node:fs";
|
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import { DEFAULT_RELAY_URL, RELAY_STATE_FILE } from "./config.ts";
|
|
11
11
|
import { logWarn } from "./logger.ts";
|
|
12
|
-
import type { ExtensionUIContext, KnownRelay, RelayState } from "./types.ts";
|
|
13
|
-
|
|
12
|
+
import type { ExtensionUIContext, KnownRelay, RelayMode, RelayState } from "./types.ts";
|
|
14
13
|
/**
|
|
15
14
|
* Load persisted relay state from disk.
|
|
16
15
|
*/
|
|
17
16
|
export function loadRelayState(): RelayState {
|
|
18
17
|
try {
|
|
19
18
|
if (!fs.existsSync(RELAY_STATE_FILE)) {
|
|
20
|
-
return { enabled: true, url: DEFAULT_RELAY_URL, relays: [] };
|
|
19
|
+
return { mode: "auto", enabled: true, url: DEFAULT_RELAY_URL, relays: [] };
|
|
21
20
|
}
|
|
22
21
|
const s = JSON.parse(fs.readFileSync(RELAY_STATE_FILE, "utf8"));
|
|
23
22
|
const relays: KnownRelay[] = Array.isArray(s?.relays) ? s.relays : [];
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const mode: RelayMode =
|
|
24
|
+
s?.mode === "on" || s?.mode === "off" || s?.mode === "auto"
|
|
25
|
+
? s.mode
|
|
26
|
+
: "auto";
|
|
27
|
+
const enabled =
|
|
28
|
+
mode === "on"
|
|
29
|
+
? true
|
|
30
|
+
: mode === "off"
|
|
31
|
+
? false
|
|
32
|
+
: s?.enabled !== false && relays.length > 0;
|
|
26
33
|
return {
|
|
34
|
+
mode,
|
|
27
35
|
enabled,
|
|
28
36
|
url: typeof s?.url === "string" ? s.url.trim() : (relays[0]?.url || DEFAULT_RELAY_URL),
|
|
29
37
|
relays,
|
|
30
38
|
};
|
|
31
39
|
} catch {
|
|
32
|
-
return { enabled: true, url: DEFAULT_RELAY_URL, relays: [] };
|
|
40
|
+
return { mode: "auto", enabled: true, url: DEFAULT_RELAY_URL, relays: [] };
|
|
33
41
|
}
|
|
34
42
|
}
|
|
35
43
|
|
|
@@ -51,13 +59,83 @@ export function saveRelayState(s: RelayState): void {
|
|
|
51
59
|
}
|
|
52
60
|
|
|
53
61
|
/**
|
|
54
|
-
* Deduplicate and add a relay URL
|
|
62
|
+
* Deduplicate and add or update a relay URL in the known relay list.
|
|
55
63
|
*/
|
|
56
|
-
export function ensureRelay(
|
|
57
|
-
|
|
58
|
-
|
|
64
|
+
export function ensureRelay(
|
|
65
|
+
s: RelayState,
|
|
66
|
+
url: string,
|
|
67
|
+
label?: string,
|
|
68
|
+
): KnownRelay {
|
|
69
|
+
const cleanUrl = (url || "").trim();
|
|
70
|
+
if (!cleanUrl) {
|
|
71
|
+
throw new Error("Relay URL cannot be empty");
|
|
72
|
+
}
|
|
73
|
+
const cleanLabel = (label || "").trim() || undefined;
|
|
74
|
+
const existing = s.relays.find((r) => r.url === cleanUrl);
|
|
75
|
+
if (existing) {
|
|
76
|
+
if (cleanLabel && cleanLabel !== "manual") {
|
|
77
|
+
existing.label = cleanLabel;
|
|
78
|
+
}
|
|
79
|
+
return existing;
|
|
80
|
+
}
|
|
81
|
+
const newRelay: KnownRelay = {
|
|
82
|
+
url: cleanUrl,
|
|
83
|
+
label: cleanLabel && cleanLabel !== "manual" ? cleanLabel : undefined,
|
|
84
|
+
addedAt: new Date().toISOString(),
|
|
85
|
+
};
|
|
86
|
+
s.relays.push(newRelay);
|
|
87
|
+
return newRelay;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Set or update short name / label for a relay by URL, index, or existing label.
|
|
92
|
+
*/
|
|
93
|
+
export function setRelayLabel(
|
|
94
|
+
s: RelayState,
|
|
95
|
+
identifier: string | number,
|
|
96
|
+
label: string,
|
|
97
|
+
): KnownRelay | null {
|
|
98
|
+
const relay = findRelay(s, identifier);
|
|
99
|
+
if (!relay) return null;
|
|
100
|
+
const cleanLabel = (label || "").trim();
|
|
101
|
+
relay.label = cleanLabel || undefined;
|
|
102
|
+
return relay;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Find a relay in state by 1-based index, short name / label, or URL.
|
|
107
|
+
*/
|
|
108
|
+
export function findRelay(
|
|
109
|
+
s: RelayState,
|
|
110
|
+
identifier: string | number,
|
|
111
|
+
): KnownRelay | undefined {
|
|
112
|
+
if (typeof identifier === "number") {
|
|
113
|
+
const idx = identifier - 1;
|
|
114
|
+
return s.relays[idx];
|
|
59
115
|
}
|
|
60
|
-
|
|
116
|
+
const str = String(identifier || "").trim();
|
|
117
|
+
if (!str) return undefined;
|
|
118
|
+
|
|
119
|
+
// 1-based index (e.g. "1", "2")
|
|
120
|
+
if (/^\d+$/.test(str)) {
|
|
121
|
+
const num = Number.parseInt(str, 10);
|
|
122
|
+
if (num >= 1 && num <= s.relays.length) {
|
|
123
|
+
return s.relays[num - 1];
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Exact URL match
|
|
128
|
+
const exactUrl = s.relays.find((r) => r.url === str);
|
|
129
|
+
if (exactUrl) return exactUrl;
|
|
130
|
+
|
|
131
|
+
// Case-insensitive label match
|
|
132
|
+
const byLabel = s.relays.find(
|
|
133
|
+
(r) => r.label && r.label.toLowerCase() === str.toLowerCase(),
|
|
134
|
+
);
|
|
135
|
+
if (byLabel) return byLabel;
|
|
136
|
+
|
|
137
|
+
// Partial URL match
|
|
138
|
+
return s.relays.find((r) => r.url.toLowerCase().includes(str.toLowerCase()));
|
|
61
139
|
}
|
|
62
140
|
|
|
63
141
|
/**
|
|
@@ -87,14 +165,13 @@ export function resolveRelayState(): RelayState {
|
|
|
87
165
|
return s;
|
|
88
166
|
}
|
|
89
167
|
|
|
90
|
-
// In-memory global relay state
|
|
91
168
|
let activeRelayState: RelayState = resolveRelayState();
|
|
92
169
|
// Monotonic counter to distribute primary relay across concurrent subagents
|
|
93
170
|
let roundRobinCounter = 0;
|
|
94
171
|
let activeStatusUi: ExtensionUIContext | null = null;
|
|
172
|
+
let isFreeFlowModelActive = true;
|
|
95
173
|
/**
|
|
96
174
|
* Mtime of the on-disk state file at the moment we last read or wrote it.
|
|
97
|
-
* Lets worker processes pick up relay-pool changes persisted by another
|
|
98
175
|
* session's master daemon, while never clobbering this process's own
|
|
99
176
|
* unpersisted runtime overrides between external writes.
|
|
100
177
|
*/
|
|
@@ -140,6 +217,12 @@ export function setStatusUi(ui: ExtensionUIContext | null): void {
|
|
|
140
217
|
activeStatusUi = ui;
|
|
141
218
|
}
|
|
142
219
|
|
|
220
|
+
export function setFreeFlowModelActive(active: boolean): void {
|
|
221
|
+
isFreeFlowModelActive = active;
|
|
222
|
+
if (!active && activeStatusUi?.setStatus) {
|
|
223
|
+
activeStatusUi.setStatus("freeflow", undefined);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
143
226
|
/**
|
|
144
227
|
* Get the current Pi extension UI context
|
|
145
228
|
*/
|
|
@@ -149,13 +232,27 @@ export function getStatusUi(): ExtensionUIContext | null {
|
|
|
149
232
|
|
|
150
233
|
/**
|
|
151
234
|
* Generate a short, human-readable label for a relay URL.
|
|
235
|
+
* Prefers user-configured label/short name; falls back to clean domain/subdomain.
|
|
152
236
|
*/
|
|
153
237
|
export function shortRelayLabel(url: string, relays?: KnownRelay[]): string {
|
|
154
238
|
const pool = relays || activeRelayState.relays;
|
|
155
239
|
try {
|
|
156
240
|
const hit = pool.find((r) => r.url === url);
|
|
157
|
-
if (hit?.label)
|
|
158
|
-
|
|
241
|
+
if (hit?.label?.trim() && hit.label.trim() !== "manual") {
|
|
242
|
+
return hit.label.trim();
|
|
243
|
+
}
|
|
244
|
+
const u = new URL(url);
|
|
245
|
+
const host = u.host;
|
|
246
|
+
// IP address (e.g. 192.168.1.5:8080 or 10.0.0.1)
|
|
247
|
+
if (/^(\d{1,3}\.){3}\d{1,3}(:\d+)?$/.test(host)) {
|
|
248
|
+
return host;
|
|
249
|
+
}
|
|
250
|
+
const parts = host.split(".");
|
|
251
|
+
if (parts.length >= 3) {
|
|
252
|
+
// E.g. "my-relay" from "my-relay.workers.dev" or "my-app.vercel.app"
|
|
253
|
+
return parts[0];
|
|
254
|
+
}
|
|
255
|
+
return parts[0] || host;
|
|
159
256
|
} catch {
|
|
160
257
|
return url.slice(0, 18);
|
|
161
258
|
}
|
|
@@ -206,13 +303,20 @@ export function updateRelayStatusUi(targetUrl?: string): void {
|
|
|
206
303
|
if (!activeStatusUi?.setStatus) {
|
|
207
304
|
return;
|
|
208
305
|
}
|
|
209
|
-
|
|
210
|
-
if (!
|
|
211
|
-
activeStatusUi.setStatus("freeflow",
|
|
306
|
+
// Do not update status bar if the user switched to another provider (e.g. Gemini/Claude)
|
|
307
|
+
if (!isFreeFlowModelActive) {
|
|
308
|
+
activeStatusUi.setStatus("freeflow", undefined);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const state = getActiveRelayState();
|
|
312
|
+
if (!state.enabled || !state.relays || state.relays.length === 0) {
|
|
313
|
+
activeStatusUi.setStatus("freeflow", undefined);
|
|
212
314
|
return;
|
|
213
315
|
}
|
|
316
|
+
const currentUrl = targetUrl || state.url || state.relays[0]?.url || "";
|
|
214
317
|
const label = shortRelayLabel(currentUrl);
|
|
215
|
-
const total =
|
|
216
|
-
const pos = Math.max(1,
|
|
217
|
-
|
|
318
|
+
const total = state.relays.length || 1;
|
|
319
|
+
const pos = Math.max(1, state.relays.findIndex((r) => r.url === currentUrl) + 1);
|
|
320
|
+
const modeLabel = state.mode === "on" ? "ON" : "AUTO (ON)";
|
|
321
|
+
activeStatusUi.setStatus("freeflow", `relay: ${modeLabel} | ${label} ${pos}/${total}`);
|
|
218
322
|
}
|
package/src/types.ts
CHANGED
|
@@ -39,13 +39,14 @@ export interface KnownRelay {
|
|
|
39
39
|
label?: string;
|
|
40
40
|
addedAt?: string;
|
|
41
41
|
}
|
|
42
|
+
export type RelayMode = "auto" | "on" | "off";
|
|
42
43
|
|
|
43
44
|
export interface RelayState {
|
|
45
|
+
mode?: RelayMode;
|
|
44
46
|
enabled: boolean;
|
|
45
47
|
url: string;
|
|
46
48
|
relays: KnownRelay[];
|
|
47
49
|
}
|
|
48
|
-
|
|
49
50
|
export type LogLevel = "debug" | "info" | "warn" | "error" | "audit";
|
|
50
51
|
|
|
51
52
|
export interface DebugState {
|