@amoshydra/oktoso 0.1.6 → 0.1.7
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/dist/apps/even-g2/assets/{index-mkmYJuX5.js → index-BgKyd-x4.js} +1 -1
- package/dist/apps/even-g2/index.html +1 -1
- package/dist/hub.js +348 -79
- package/dist/hub.js.map +1 -1
- package/dist/web/assets/index-DdhoGMJH.css +1 -0
- package/dist/web/assets/{index-CeSlSYL3.js → index-itfSawL7.js} +3 -3
- package/dist/web/assets/index-itfSawL7.js.map +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-Bp3hsoUF.css +0 -1
- package/dist/web/assets/index-CeSlSYL3.js.map +0 -1
package/dist/hub.js
CHANGED
|
@@ -8,7 +8,7 @@ var __export = (target, all) => {
|
|
|
8
8
|
// packages/hub/src/main.ts
|
|
9
9
|
import { spawn } from "child_process";
|
|
10
10
|
import { existsSync as existsSync4 } from "fs";
|
|
11
|
-
import { dirname as
|
|
11
|
+
import { dirname as dirname5, join as join4, resolve as resolve3 } from "path";
|
|
12
12
|
import { fileURLToPath } from "url";
|
|
13
13
|
|
|
14
14
|
// node_modules/.pnpm/@hono+node-server@2.1.5_hono@4.13.14/node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
|
|
@@ -429,7 +429,7 @@ var readBodyDirect = (request) => {
|
|
|
429
429
|
request[bodyBufferKey] = buffered;
|
|
430
430
|
return Promise.resolve(buffered);
|
|
431
431
|
}
|
|
432
|
-
const promise2 = new Promise((
|
|
432
|
+
const promise2 = new Promise((resolve4, reject) => {
|
|
433
433
|
const chunks = [];
|
|
434
434
|
let settled = false;
|
|
435
435
|
const finish = (callback) => {
|
|
@@ -447,7 +447,7 @@ var readBodyDirect = (request) => {
|
|
|
447
447
|
else if (recovered === void 0) reject(error62 ?? normalizeAbortError(request, incoming));
|
|
448
448
|
else {
|
|
449
449
|
request[bodyBufferKey] = recovered;
|
|
450
|
-
|
|
450
|
+
resolve4(recovered);
|
|
451
451
|
}
|
|
452
452
|
});
|
|
453
453
|
return true;
|
|
@@ -459,7 +459,7 @@ var readBodyDirect = (request) => {
|
|
|
459
459
|
finish(() => {
|
|
460
460
|
const buffer = chunks.length === 1 ? chunks[0] : Buffer.concat(chunks);
|
|
461
461
|
request[bodyBufferKey] = buffer;
|
|
462
|
-
|
|
462
|
+
resolve4(buffer);
|
|
463
463
|
});
|
|
464
464
|
};
|
|
465
465
|
const onError = (error62) => {
|
|
@@ -1006,7 +1006,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
1006
1006
|
});
|
|
1007
1007
|
if (!chunk) {
|
|
1008
1008
|
if (i === 1) {
|
|
1009
|
-
await new Promise((
|
|
1009
|
+
await new Promise((resolve4) => setTimeout(resolve4));
|
|
1010
1010
|
maxReadCount = 3;
|
|
1011
1011
|
continue;
|
|
1012
1012
|
}
|
|
@@ -1192,9 +1192,9 @@ var setupWebSocket = (options) => {
|
|
|
1192
1192
|
}
|
|
1193
1193
|
};
|
|
1194
1194
|
const waitForWebSocket = (request, connectionSymbol) => {
|
|
1195
|
-
return new Promise((
|
|
1195
|
+
return new Promise((resolve4, reject) => {
|
|
1196
1196
|
waiterMap.set(request, {
|
|
1197
|
-
resolve:
|
|
1197
|
+
resolve: resolve4,
|
|
1198
1198
|
reject,
|
|
1199
1199
|
connectionSymbol
|
|
1200
1200
|
});
|
|
@@ -6411,7 +6411,7 @@ var recursive = /* @__PURE__ */ new WeakMap();
|
|
|
6411
6411
|
var NONE = 0;
|
|
6412
6412
|
var ASSUMED = 1;
|
|
6413
6413
|
var PROVEN = 2;
|
|
6414
|
-
function isRecursive(inst, stack,
|
|
6414
|
+
function isRecursive(inst, stack, resolve4) {
|
|
6415
6415
|
const cached2 = recursive.get(inst);
|
|
6416
6416
|
if (cached2 !== void 0)
|
|
6417
6417
|
return cached2 ? PROVEN : NONE;
|
|
@@ -6421,7 +6421,7 @@ function isRecursive(inst, stack, resolve3) {
|
|
|
6421
6421
|
let result = NONE;
|
|
6422
6422
|
const check2 = (child) => {
|
|
6423
6423
|
if (result !== PROVEN && child?._zod) {
|
|
6424
|
-
const answer = isRecursive(child, stack,
|
|
6424
|
+
const answer = isRecursive(child, stack, resolve4);
|
|
6425
6425
|
if (answer > result)
|
|
6426
6426
|
result = answer;
|
|
6427
6427
|
}
|
|
@@ -6432,7 +6432,7 @@ function isRecursive(inst, stack, resolve3) {
|
|
|
6432
6432
|
const desc = Object.getOwnPropertyDescriptor(sh, key);
|
|
6433
6433
|
if (spread && !desc.enumerable)
|
|
6434
6434
|
continue;
|
|
6435
|
-
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack,
|
|
6435
|
+
const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve4) : NONE;
|
|
6436
6436
|
if (child > answer)
|
|
6437
6437
|
answer = child;
|
|
6438
6438
|
}
|
|
@@ -6496,7 +6496,7 @@ function isRecursive(inst, stack, resolve3) {
|
|
|
6496
6496
|
break;
|
|
6497
6497
|
// `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
|
|
6498
6498
|
case "lazy": {
|
|
6499
|
-
const inner = def._cachedInner ?? (
|
|
6499
|
+
const inner = def._cachedInner ?? (resolve4 ? inst._zod.innerType : void 0);
|
|
6500
6500
|
merge2(inner ? isRecursive(inner, stack, false) : ASSUMED);
|
|
6501
6501
|
break;
|
|
6502
6502
|
}
|
|
@@ -21078,6 +21078,51 @@ var zError = external_exports.object({
|
|
|
21078
21078
|
})
|
|
21079
21079
|
});
|
|
21080
21080
|
var zProviderStatus = external_exports.enum(["online", "offline", "auth-error", "unknown"]);
|
|
21081
|
+
var zProviderAuthKind = external_exports.enum(["basic", "bearer", "none"]);
|
|
21082
|
+
var zProviderIDInput = external_exports.string().min(1).max(64).regex(
|
|
21083
|
+
/^[a-zA-Z0-9._-]+$/,
|
|
21084
|
+
"Provider IDs may contain letters, numbers, dot, underscore, or dash"
|
|
21085
|
+
);
|
|
21086
|
+
var zProviderTypeInput = external_exports.enum(["opencode", "openwebui"]);
|
|
21087
|
+
var zProviderAuthInput = external_exports.discriminatedUnion("kind", [
|
|
21088
|
+
external_exports.object({
|
|
21089
|
+
kind: external_exports.literal("basic"),
|
|
21090
|
+
username: external_exports.string().min(1).default("opencode"),
|
|
21091
|
+
password: external_exports.string().min(1)
|
|
21092
|
+
}),
|
|
21093
|
+
external_exports.object({
|
|
21094
|
+
kind: external_exports.literal("bearer"),
|
|
21095
|
+
token: external_exports.string().min(1)
|
|
21096
|
+
}),
|
|
21097
|
+
external_exports.object({ kind: external_exports.literal("none") })
|
|
21098
|
+
]);
|
|
21099
|
+
var zProviderAuthUpdate = external_exports.discriminatedUnion("kind", [
|
|
21100
|
+
external_exports.object({
|
|
21101
|
+
kind: external_exports.literal("basic"),
|
|
21102
|
+
username: external_exports.string().min(1).default("opencode"),
|
|
21103
|
+
password: external_exports.string().min(1).optional()
|
|
21104
|
+
}),
|
|
21105
|
+
external_exports.object({
|
|
21106
|
+
kind: external_exports.literal("bearer"),
|
|
21107
|
+
token: external_exports.string().min(1).optional()
|
|
21108
|
+
}),
|
|
21109
|
+
external_exports.object({ kind: external_exports.literal("none") })
|
|
21110
|
+
]);
|
|
21111
|
+
var zProviderInput = external_exports.object({
|
|
21112
|
+
id: zProviderIDInput,
|
|
21113
|
+
type: zProviderTypeInput,
|
|
21114
|
+
name: external_exports.string().min(1).max(80).optional(),
|
|
21115
|
+
baseUrl: external_exports.string().url(),
|
|
21116
|
+
auth: zProviderAuthInput.optional(),
|
|
21117
|
+
enabled: external_exports.boolean().default(true)
|
|
21118
|
+
});
|
|
21119
|
+
var zProviderUpdateInput = external_exports.object({
|
|
21120
|
+
type: zProviderTypeInput,
|
|
21121
|
+
name: external_exports.string().min(1).max(80).optional(),
|
|
21122
|
+
baseUrl: external_exports.string().url(),
|
|
21123
|
+
auth: zProviderAuthUpdate.optional(),
|
|
21124
|
+
enabled: external_exports.boolean().default(true)
|
|
21125
|
+
});
|
|
21081
21126
|
var zProviderCapabilities = external_exports.object({
|
|
21082
21127
|
list: external_exports.boolean(),
|
|
21083
21128
|
read: external_exports.boolean(),
|
|
@@ -21098,6 +21143,8 @@ var zProvider = external_exports.object({
|
|
|
21098
21143
|
name: external_exports.string(),
|
|
21099
21144
|
baseUrl: external_exports.string(),
|
|
21100
21145
|
enabled: external_exports.boolean(),
|
|
21146
|
+
authKind: zProviderAuthKind.optional(),
|
|
21147
|
+
authUsername: external_exports.string().optional(),
|
|
21101
21148
|
status: zProviderStatus,
|
|
21102
21149
|
version: external_exports.string().optional(),
|
|
21103
21150
|
lastCheckedAt: external_exports.number().int().optional(),
|
|
@@ -21935,14 +21982,14 @@ async function probeHub(config2) {
|
|
|
21935
21982
|
}
|
|
21936
21983
|
function isPortOpen(config2) {
|
|
21937
21984
|
const host = isWildcardHost(config2.host) ? "127.0.0.1" : formatHost(config2.host).replace(/^\[|\]$/g, "");
|
|
21938
|
-
return new Promise((
|
|
21985
|
+
return new Promise((resolve4) => {
|
|
21939
21986
|
const socket = connect({ host, port: config2.port });
|
|
21940
21987
|
let settled = false;
|
|
21941
21988
|
const done = (open3) => {
|
|
21942
21989
|
if (settled) return;
|
|
21943
21990
|
settled = true;
|
|
21944
21991
|
socket.destroy();
|
|
21945
|
-
|
|
21992
|
+
resolve4(open3);
|
|
21946
21993
|
};
|
|
21947
21994
|
socket.setTimeout(400);
|
|
21948
21995
|
socket.once("connect", () => done(true));
|
|
@@ -22032,10 +22079,10 @@ var RotatingFileSink = class {
|
|
|
22032
22079
|
async close() {
|
|
22033
22080
|
this.closed = true;
|
|
22034
22081
|
while (this.running || this.queue.length > 0) {
|
|
22035
|
-
await new Promise((
|
|
22082
|
+
await new Promise((resolve4) => setTimeout(resolve4, 5));
|
|
22036
22083
|
}
|
|
22037
22084
|
if (this.stream) {
|
|
22038
|
-
await new Promise((
|
|
22085
|
+
await new Promise((resolve4) => this.stream?.end(resolve4));
|
|
22039
22086
|
this.stream = void 0;
|
|
22040
22087
|
}
|
|
22041
22088
|
}
|
|
@@ -22088,8 +22135,8 @@ var RotatingFileSink = class {
|
|
|
22088
22135
|
flags: "a",
|
|
22089
22136
|
mode: 384
|
|
22090
22137
|
});
|
|
22091
|
-
await new Promise((
|
|
22092
|
-
this.stream?.once("open", () =>
|
|
22138
|
+
await new Promise((resolve4, reject) => {
|
|
22139
|
+
this.stream?.once("open", () => resolve4());
|
|
22093
22140
|
this.stream?.once("error", reject);
|
|
22094
22141
|
});
|
|
22095
22142
|
await chmod(this.filePath, 384).catch(() => void 0);
|
|
@@ -22099,29 +22146,29 @@ var RotatingFileSink = class {
|
|
|
22099
22146
|
if (!this.stream) return;
|
|
22100
22147
|
const stream2 = this.stream;
|
|
22101
22148
|
this.stream = void 0;
|
|
22102
|
-
await new Promise((
|
|
22149
|
+
await new Promise((resolve4) => stream2.end(resolve4));
|
|
22103
22150
|
await rm(this.previousPath, { force: true });
|
|
22104
22151
|
await rename2(this.filePath, this.previousPath);
|
|
22105
22152
|
this.stream = createWriteStream(this.filePath, {
|
|
22106
22153
|
flags: "a",
|
|
22107
22154
|
mode: 384
|
|
22108
22155
|
});
|
|
22109
|
-
await new Promise((
|
|
22110
|
-
this.stream?.once("open", () =>
|
|
22156
|
+
await new Promise((resolve4, reject) => {
|
|
22157
|
+
this.stream?.once("open", () => resolve4());
|
|
22111
22158
|
this.stream?.once("error", reject);
|
|
22112
22159
|
});
|
|
22113
22160
|
await chmod(this.filePath, 384).catch(() => void 0);
|
|
22114
22161
|
this.size = 0;
|
|
22115
22162
|
}
|
|
22116
22163
|
writeLine(line) {
|
|
22117
|
-
return new Promise((
|
|
22164
|
+
return new Promise((resolve4, reject) => {
|
|
22118
22165
|
if (!this.stream) {
|
|
22119
22166
|
reject(new Error("Log stream is not open"));
|
|
22120
22167
|
return;
|
|
22121
22168
|
}
|
|
22122
22169
|
this.stream.write(line, (error62) => {
|
|
22123
22170
|
if (error62) reject(error62);
|
|
22124
|
-
else
|
|
22171
|
+
else resolve4();
|
|
22125
22172
|
});
|
|
22126
22173
|
});
|
|
22127
22174
|
}
|
|
@@ -24968,17 +25015,17 @@ function findSessionID(value, depth = 0) {
|
|
|
24968
25015
|
return void 0;
|
|
24969
25016
|
}
|
|
24970
25017
|
async function delay(ms, signal) {
|
|
24971
|
-
await new Promise((
|
|
25018
|
+
await new Promise((resolve4) => {
|
|
24972
25019
|
if (signal.aborted) {
|
|
24973
|
-
|
|
25020
|
+
resolve4();
|
|
24974
25021
|
return;
|
|
24975
25022
|
}
|
|
24976
|
-
const timer = setTimeout(
|
|
25023
|
+
const timer = setTimeout(resolve4, ms);
|
|
24977
25024
|
signal.addEventListener(
|
|
24978
25025
|
"abort",
|
|
24979
25026
|
() => {
|
|
24980
25027
|
clearTimeout(timer);
|
|
24981
|
-
|
|
25028
|
+
resolve4();
|
|
24982
25029
|
},
|
|
24983
25030
|
{ once: true }
|
|
24984
25031
|
);
|
|
@@ -25592,17 +25639,17 @@ function decodeCursor(cursor) {
|
|
|
25592
25639
|
}
|
|
25593
25640
|
}
|
|
25594
25641
|
async function delay2(ms, signal) {
|
|
25595
|
-
await new Promise((
|
|
25642
|
+
await new Promise((resolve4) => {
|
|
25596
25643
|
if (signal.aborted) {
|
|
25597
|
-
|
|
25644
|
+
resolve4();
|
|
25598
25645
|
return;
|
|
25599
25646
|
}
|
|
25600
|
-
const timer = setTimeout(
|
|
25647
|
+
const timer = setTimeout(resolve4, ms);
|
|
25601
25648
|
signal.addEventListener(
|
|
25602
25649
|
"abort",
|
|
25603
25650
|
() => {
|
|
25604
25651
|
clearTimeout(timer);
|
|
25605
|
-
|
|
25652
|
+
resolve4();
|
|
25606
25653
|
},
|
|
25607
25654
|
{ once: true }
|
|
25608
25655
|
);
|
|
@@ -25619,28 +25666,46 @@ var ProviderRegistry = class {
|
|
|
25619
25666
|
constructor(providers, logger) {
|
|
25620
25667
|
this.logger = logger;
|
|
25621
25668
|
for (const config2 of providers) {
|
|
25622
|
-
this.runtimes.set(config2.id,
|
|
25623
|
-
config: config2,
|
|
25624
|
-
status: "unknown",
|
|
25625
|
-
...config2.type === "opencode" ? {
|
|
25626
|
-
adapter: new OpenCodeAdapter({
|
|
25627
|
-
id: config2.id,
|
|
25628
|
-
baseUrl: config2.baseUrl,
|
|
25629
|
-
username: config2.auth?.kind === "basic" ? config2.auth.username : void 0,
|
|
25630
|
-
password: config2.auth?.kind === "basic" ? config2.auth.password : ""
|
|
25631
|
-
})
|
|
25632
|
-
} : config2.type === "openwebui" ? {
|
|
25633
|
-
adapter: new OpenWebUIAdapter({
|
|
25634
|
-
id: config2.id,
|
|
25635
|
-
baseUrl: config2.baseUrl,
|
|
25636
|
-
token: config2.auth?.kind === "bearer" ? config2.auth.token : ""
|
|
25637
|
-
})
|
|
25638
|
-
} : {}
|
|
25639
|
-
});
|
|
25669
|
+
this.runtimes.set(config2.id, this.createRuntime(config2));
|
|
25640
25670
|
}
|
|
25641
25671
|
}
|
|
25642
25672
|
logger;
|
|
25643
25673
|
runtimes = /* @__PURE__ */ new Map();
|
|
25674
|
+
add(config2) {
|
|
25675
|
+
if (this.runtimes.has(config2.id)) {
|
|
25676
|
+
throw new Error(`Provider ${config2.id} is already configured`);
|
|
25677
|
+
}
|
|
25678
|
+
this.runtimes.set(config2.id, this.createRuntime(config2));
|
|
25679
|
+
}
|
|
25680
|
+
remove(id) {
|
|
25681
|
+
this.runtimes.delete(id);
|
|
25682
|
+
}
|
|
25683
|
+
replace(config2) {
|
|
25684
|
+
if (!this.runtimes.has(config2.id)) {
|
|
25685
|
+
throw new Error(`Provider ${config2.id} is not configured`);
|
|
25686
|
+
}
|
|
25687
|
+
this.runtimes.set(config2.id, this.createRuntime(config2));
|
|
25688
|
+
}
|
|
25689
|
+
createRuntime(config2) {
|
|
25690
|
+
return {
|
|
25691
|
+
config: config2,
|
|
25692
|
+
status: "unknown",
|
|
25693
|
+
...config2.type === "opencode" ? {
|
|
25694
|
+
adapter: new OpenCodeAdapter({
|
|
25695
|
+
id: config2.id,
|
|
25696
|
+
baseUrl: config2.baseUrl,
|
|
25697
|
+
username: config2.auth?.kind === "basic" ? config2.auth.username : void 0,
|
|
25698
|
+
password: config2.auth?.kind === "basic" ? config2.auth.password : ""
|
|
25699
|
+
})
|
|
25700
|
+
} : config2.type === "openwebui" ? {
|
|
25701
|
+
adapter: new OpenWebUIAdapter({
|
|
25702
|
+
id: config2.id,
|
|
25703
|
+
baseUrl: config2.baseUrl,
|
|
25704
|
+
token: config2.auth?.kind === "bearer" ? config2.auth.token : ""
|
|
25705
|
+
})
|
|
25706
|
+
} : {}
|
|
25707
|
+
};
|
|
25708
|
+
}
|
|
25644
25709
|
adapters() {
|
|
25645
25710
|
return [...this.runtimes.values()].map((runtime) => runtime.adapter).filter((adapter) => Boolean(adapter));
|
|
25646
25711
|
}
|
|
@@ -25743,6 +25808,8 @@ var ProviderRegistry = class {
|
|
|
25743
25808
|
name: runtime.config.name ?? runtime.config.id,
|
|
25744
25809
|
baseUrl: runtime.config.baseUrl,
|
|
25745
25810
|
enabled: runtime.config.enabled,
|
|
25811
|
+
authKind: runtime.config.auth?.kind ?? "none",
|
|
25812
|
+
...runtime.config.auth?.kind === "basic" ? { authUsername: runtime.config.auth.username } : {},
|
|
25746
25813
|
status: runtime.status,
|
|
25747
25814
|
...runtime.version ? { version: runtime.version } : {},
|
|
25748
25815
|
...runtime.lastCheckedAt ? { lastCheckedAt: runtime.lastCheckedAt } : {},
|
|
@@ -25751,6 +25818,10 @@ var ProviderRegistry = class {
|
|
|
25751
25818
|
}
|
|
25752
25819
|
};
|
|
25753
25820
|
|
|
25821
|
+
// packages/hub/src/services/provider-admin.ts
|
|
25822
|
+
import { mkdir as mkdir3, readFile as readFile3, rename as rename3, writeFile as writeFile2 } from "fs/promises";
|
|
25823
|
+
import { dirname as dirname4, resolve } from "path";
|
|
25824
|
+
|
|
25754
25825
|
// packages/hub/src/domain/errors.ts
|
|
25755
25826
|
var AppError = class extends Error {
|
|
25756
25827
|
code;
|
|
@@ -25782,6 +25853,157 @@ function conflict(message, details) {
|
|
|
25782
25853
|
return new AppError("conflict", message, 409, { details });
|
|
25783
25854
|
}
|
|
25784
25855
|
|
|
25856
|
+
// packages/hub/src/services/provider-admin.ts
|
|
25857
|
+
var ProviderAdminService = class {
|
|
25858
|
+
constructor(registry2, sessions, attention, providerEvents, logger, configPath) {
|
|
25859
|
+
this.registry = registry2;
|
|
25860
|
+
this.sessions = sessions;
|
|
25861
|
+
this.attention = attention;
|
|
25862
|
+
this.providerEvents = providerEvents;
|
|
25863
|
+
this.logger = logger;
|
|
25864
|
+
this.configPath = resolve(configPath);
|
|
25865
|
+
}
|
|
25866
|
+
registry;
|
|
25867
|
+
sessions;
|
|
25868
|
+
attention;
|
|
25869
|
+
providerEvents;
|
|
25870
|
+
logger;
|
|
25871
|
+
configPath;
|
|
25872
|
+
queue = Promise.resolve();
|
|
25873
|
+
add(input2) {
|
|
25874
|
+
return this.serialize(async () => {
|
|
25875
|
+
const raw2 = await this.read();
|
|
25876
|
+
const providers = raw2.providers ?? [];
|
|
25877
|
+
if (providers.some((provider) => provider.id === input2.id)) {
|
|
25878
|
+
throw conflict(`Provider ${input2.id} is already configured`);
|
|
25879
|
+
}
|
|
25880
|
+
const config2 = this.toConfig(input2);
|
|
25881
|
+
await this.write({
|
|
25882
|
+
...raw2,
|
|
25883
|
+
providers: [...providers, config2]
|
|
25884
|
+
});
|
|
25885
|
+
this.registry.add(config2);
|
|
25886
|
+
this.invalidate();
|
|
25887
|
+
const adapter = this.registry.adapter(config2.id);
|
|
25888
|
+
if (adapter) void this.providerEvents?.subscribe(adapter);
|
|
25889
|
+
const saved = await this.check(config2.id);
|
|
25890
|
+
this.logger.info("provider added", {
|
|
25891
|
+
providerID: config2.id,
|
|
25892
|
+
status: saved.status
|
|
25893
|
+
});
|
|
25894
|
+
return saved;
|
|
25895
|
+
});
|
|
25896
|
+
}
|
|
25897
|
+
update(id, input2) {
|
|
25898
|
+
return this.serialize(async () => {
|
|
25899
|
+
const raw2 = await this.read();
|
|
25900
|
+
const providers = raw2.providers ?? [];
|
|
25901
|
+
const index = providers.findIndex((provider) => provider.id === id);
|
|
25902
|
+
if (index < 0) throw notFound(`Provider ${id} is not configured`);
|
|
25903
|
+
const current = providers[index];
|
|
25904
|
+
const auth = input2.auth ? this.mergeAuth(current.auth, input2.auth) : current.auth;
|
|
25905
|
+
this.assertTypeAuth(input2.type, auth);
|
|
25906
|
+
const config2 = zProviderConfig.parse({
|
|
25907
|
+
...current,
|
|
25908
|
+
type: input2.type,
|
|
25909
|
+
...input2.name ? { name: input2.name } : {},
|
|
25910
|
+
baseUrl: input2.baseUrl,
|
|
25911
|
+
enabled: input2.enabled,
|
|
25912
|
+
...auth ? { auth } : {}
|
|
25913
|
+
});
|
|
25914
|
+
const next = [...providers];
|
|
25915
|
+
next[index] = config2;
|
|
25916
|
+
await this.write({ ...raw2, providers: next });
|
|
25917
|
+
this.registry.replace(config2);
|
|
25918
|
+
this.invalidate();
|
|
25919
|
+
const adapter = this.registry.adapter(config2.id);
|
|
25920
|
+
if (adapter) void this.providerEvents?.subscribe(adapter);
|
|
25921
|
+
const saved = await this.check(config2.id);
|
|
25922
|
+
this.logger.info("provider updated", {
|
|
25923
|
+
providerID: config2.id,
|
|
25924
|
+
status: saved.status
|
|
25925
|
+
});
|
|
25926
|
+
return saved;
|
|
25927
|
+
});
|
|
25928
|
+
}
|
|
25929
|
+
toConfig(input2) {
|
|
25930
|
+
this.assertTypeAuth(input2.type, input2.auth);
|
|
25931
|
+
return zProviderConfig.parse({
|
|
25932
|
+
id: input2.id,
|
|
25933
|
+
type: input2.type,
|
|
25934
|
+
...input2.name ? { name: input2.name } : {},
|
|
25935
|
+
baseUrl: input2.baseUrl,
|
|
25936
|
+
...input2.auth ? { auth: input2.auth } : {},
|
|
25937
|
+
enabled: input2.enabled
|
|
25938
|
+
});
|
|
25939
|
+
}
|
|
25940
|
+
assertTypeAuth(type, auth) {
|
|
25941
|
+
if (!auth || auth.kind === "none") return;
|
|
25942
|
+
if (type === "openwebui" && auth.kind !== "bearer") {
|
|
25943
|
+
throw badRequest("Open WebUI requires bearer authentication");
|
|
25944
|
+
}
|
|
25945
|
+
if (type === "opencode" && auth.kind !== "basic") {
|
|
25946
|
+
throw badRequest("OpenCode requires basic authentication");
|
|
25947
|
+
}
|
|
25948
|
+
}
|
|
25949
|
+
mergeAuth(current, input2) {
|
|
25950
|
+
if (input2.kind === "none") return { kind: "none" };
|
|
25951
|
+
if (input2.kind === "basic") {
|
|
25952
|
+
const password = input2.password ?? (current?.kind === "basic" ? current.password : void 0);
|
|
25953
|
+
if (!password) {
|
|
25954
|
+
throw badRequest("A password is required for basic authentication");
|
|
25955
|
+
}
|
|
25956
|
+
return { kind: "basic", username: input2.username, password };
|
|
25957
|
+
}
|
|
25958
|
+
const token = input2.token ?? (current?.kind === "bearer" ? current.token : void 0);
|
|
25959
|
+
if (!token) {
|
|
25960
|
+
throw badRequest("A token is required for bearer authentication");
|
|
25961
|
+
}
|
|
25962
|
+
return { kind: "bearer", token };
|
|
25963
|
+
}
|
|
25964
|
+
invalidate() {
|
|
25965
|
+
this.sessions.invalidate();
|
|
25966
|
+
this.attention.invalidate();
|
|
25967
|
+
}
|
|
25968
|
+
async check(id) {
|
|
25969
|
+
try {
|
|
25970
|
+
await this.registry.check(id);
|
|
25971
|
+
} catch (error62) {
|
|
25972
|
+
this.logger.warn("provider health check failed", {
|
|
25973
|
+
providerID: id,
|
|
25974
|
+
error: error62 instanceof Error ? error62.message : String(error62)
|
|
25975
|
+
});
|
|
25976
|
+
}
|
|
25977
|
+
const provider = this.registry.get(id);
|
|
25978
|
+
if (!provider) throw notFound(`Provider ${id} is not configured`);
|
|
25979
|
+
return provider;
|
|
25980
|
+
}
|
|
25981
|
+
async read() {
|
|
25982
|
+
const raw2 = JSON.parse(await readFile3(this.configPath, "utf8"));
|
|
25983
|
+
if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2)) {
|
|
25984
|
+
throw badRequest("Hub config must be a JSON object");
|
|
25985
|
+
}
|
|
25986
|
+
return raw2;
|
|
25987
|
+
}
|
|
25988
|
+
async write(config2) {
|
|
25989
|
+
const content = `${JSON.stringify(config2, null, 2)}
|
|
25990
|
+
`;
|
|
25991
|
+
const directory = dirname4(this.configPath);
|
|
25992
|
+
await mkdir3(directory, { recursive: true });
|
|
25993
|
+
const tempPath = `${this.configPath}.${process.pid}.${Date.now()}.tmp`;
|
|
25994
|
+
await writeFile2(tempPath, content, { mode: 384 });
|
|
25995
|
+
await rename3(tempPath, this.configPath);
|
|
25996
|
+
}
|
|
25997
|
+
serialize(action) {
|
|
25998
|
+
const run2 = this.queue.then(action, action);
|
|
25999
|
+
this.queue = run2.then(
|
|
26000
|
+
() => void 0,
|
|
26001
|
+
() => void 0
|
|
26002
|
+
);
|
|
26003
|
+
return run2;
|
|
26004
|
+
}
|
|
26005
|
+
};
|
|
26006
|
+
|
|
25785
26007
|
// packages/hub/src/services/session-service.ts
|
|
25786
26008
|
var DEFAULT_LIMIT = 50;
|
|
25787
26009
|
var MAX_LIMIT = 200;
|
|
@@ -26042,7 +26264,7 @@ var ProviderEventService = class {
|
|
|
26042
26264
|
coalesceMs;
|
|
26043
26265
|
messageCoalesceMs;
|
|
26044
26266
|
controller = new AbortController();
|
|
26045
|
-
|
|
26267
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
26046
26268
|
timers = /* @__PURE__ */ new Map();
|
|
26047
26269
|
messageTimers = /* @__PURE__ */ new Map();
|
|
26048
26270
|
pendingMessages = /* @__PURE__ */ new Map();
|
|
@@ -26051,29 +26273,43 @@ var ProviderEventService = class {
|
|
|
26051
26273
|
if (this.started) return;
|
|
26052
26274
|
this.started = true;
|
|
26053
26275
|
for (const adapter of this.registry.adapters()) {
|
|
26054
|
-
|
|
26055
|
-
|
|
26056
|
-
|
|
26057
|
-
|
|
26058
|
-
|
|
26059
|
-
|
|
26060
|
-
|
|
26061
|
-
|
|
26062
|
-
this.
|
|
26063
|
-
|
|
26064
|
-
|
|
26065
|
-
|
|
26066
|
-
|
|
26276
|
+
await this.subscribe(adapter);
|
|
26277
|
+
}
|
|
26278
|
+
}
|
|
26279
|
+
async subscribe(adapter) {
|
|
26280
|
+
if (!this.started || !adapter.subscribe) return;
|
|
26281
|
+
this.unsubscribe(adapter.id);
|
|
26282
|
+
try {
|
|
26283
|
+
const unsubscribe = await adapter.subscribe(
|
|
26284
|
+
(event) => this.handle(event),
|
|
26285
|
+
this.controller.signal
|
|
26286
|
+
);
|
|
26287
|
+
this.subscriptions.set(adapter.id, unsubscribe);
|
|
26288
|
+
} catch (error62) {
|
|
26289
|
+
this.logger.warn("Provider event subscription failed", {
|
|
26290
|
+
providerID: adapter.id,
|
|
26291
|
+
error: error62 instanceof Error ? error62.message : String(error62)
|
|
26292
|
+
});
|
|
26293
|
+
}
|
|
26294
|
+
}
|
|
26295
|
+
unsubscribe(id) {
|
|
26296
|
+
const unsubscribe = this.subscriptions.get(id);
|
|
26297
|
+
if (!unsubscribe) return;
|
|
26298
|
+
this.subscriptions.delete(id);
|
|
26299
|
+
try {
|
|
26300
|
+
unsubscribe();
|
|
26301
|
+
} catch {
|
|
26067
26302
|
}
|
|
26068
26303
|
}
|
|
26069
26304
|
stop() {
|
|
26070
26305
|
this.controller.abort();
|
|
26071
|
-
for (const unsubscribe of this.
|
|
26306
|
+
for (const unsubscribe of this.subscriptions.values()) {
|
|
26072
26307
|
try {
|
|
26073
26308
|
unsubscribe();
|
|
26074
26309
|
} catch {
|
|
26075
26310
|
}
|
|
26076
26311
|
}
|
|
26312
|
+
this.subscriptions.clear();
|
|
26077
26313
|
for (const timer of this.timers.values()) clearTimeout(timer);
|
|
26078
26314
|
for (const timer of this.messageTimers.values()) clearTimeout(timer);
|
|
26079
26315
|
this.timers.clear();
|
|
@@ -27296,8 +27532,8 @@ var QuestionService = class {
|
|
|
27296
27532
|
|
|
27297
27533
|
// packages/hub/src/app.ts
|
|
27298
27534
|
import { existsSync as existsSync3 } from "fs";
|
|
27299
|
-
import { readFile as
|
|
27300
|
-
import { join as join3, resolve } from "path";
|
|
27535
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
27536
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
27301
27537
|
|
|
27302
27538
|
// node_modules/.pnpm/hono@4.13.14/node_modules/hono/dist/request/constants.js
|
|
27303
27539
|
var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
|
|
@@ -29456,7 +29692,7 @@ var IdempotencyService = class {
|
|
|
29456
29692
|
};
|
|
29457
29693
|
|
|
29458
29694
|
// package.json
|
|
29459
|
-
var version2 = "0.1.
|
|
29695
|
+
var version2 = "0.1.7";
|
|
29460
29696
|
|
|
29461
29697
|
// packages/hub/src/version.ts
|
|
29462
29698
|
var VERSION = version2;
|
|
@@ -29978,8 +30214,32 @@ function registerPairRoutes(app, deps) {
|
|
|
29978
30214
|
}
|
|
29979
30215
|
|
|
29980
30216
|
// packages/hub/src/http/routes/providers.ts
|
|
29981
|
-
function registerProviderRoutes(app, registry2) {
|
|
30217
|
+
function registerProviderRoutes(app, registry2, admin) {
|
|
29982
30218
|
app.get("/api/v1/providers", (c) => c.json({ data: registry2.list() }));
|
|
30219
|
+
app.post("/api/v1/providers", async (c) => {
|
|
30220
|
+
if (!admin) throw notImplemented("Provider management is not configured");
|
|
30221
|
+
const body = await c.req.json().catch(() => void 0);
|
|
30222
|
+
const parsed = zProviderInput.safeParse(body);
|
|
30223
|
+
if (!parsed.success) {
|
|
30224
|
+
throw badRequest("Invalid provider input", {
|
|
30225
|
+
issues: parsed.error.issues
|
|
30226
|
+
});
|
|
30227
|
+
}
|
|
30228
|
+
return c.json({ data: await admin.add(parsed.data) }, 201);
|
|
30229
|
+
});
|
|
30230
|
+
app.put("/api/v1/providers/:providerID", async (c) => {
|
|
30231
|
+
if (!admin) throw notImplemented("Provider management is not configured");
|
|
30232
|
+
const providerID = c.req.param("providerID");
|
|
30233
|
+
if (!providerID) throw notFound("Provider not found");
|
|
30234
|
+
const body = await c.req.json().catch(() => void 0);
|
|
30235
|
+
const parsed = zProviderUpdateInput.safeParse(body);
|
|
30236
|
+
if (!parsed.success) {
|
|
30237
|
+
throw badRequest("Invalid provider input", {
|
|
30238
|
+
issues: parsed.error.issues
|
|
30239
|
+
});
|
|
30240
|
+
}
|
|
30241
|
+
return c.json({ data: await admin.update(providerID, parsed.data) });
|
|
30242
|
+
});
|
|
29983
30243
|
app.post("/api/v1/providers/:providerID/check", async (c) => {
|
|
29984
30244
|
const providerID = c.req.param("providerID");
|
|
29985
30245
|
if (!registry2.get(providerID)) throw notFound("Provider not found");
|
|
@@ -30558,7 +30818,7 @@ function createApp(deps) {
|
|
|
30558
30818
|
});
|
|
30559
30819
|
registerAuthRoutes(app, { auth, version: VERSION });
|
|
30560
30820
|
registerPairRoutes(app, { auth });
|
|
30561
|
-
registerProviderRoutes(app, deps.registry);
|
|
30821
|
+
registerProviderRoutes(app, deps.registry, deps.providerAdmin);
|
|
30562
30822
|
registerSessionRoutes(
|
|
30563
30823
|
app,
|
|
30564
30824
|
deps.sessions,
|
|
@@ -30578,7 +30838,7 @@ function createApp(deps) {
|
|
|
30578
30838
|
store: deps.store
|
|
30579
30839
|
});
|
|
30580
30840
|
if (deps.appsRoot) {
|
|
30581
|
-
const appsRoot =
|
|
30841
|
+
const appsRoot = resolve2(deps.appsRoot);
|
|
30582
30842
|
app.get(
|
|
30583
30843
|
"/apps/:app",
|
|
30584
30844
|
(c) => c.redirect(`/apps/${encodeURIComponent(c.req.param("app"))}/`, 308)
|
|
@@ -30597,12 +30857,12 @@ function createApp(deps) {
|
|
|
30597
30857
|
if (leaf.includes(".")) return next();
|
|
30598
30858
|
const indexPath = join3(appsRoot, appID, "index.html");
|
|
30599
30859
|
if (!existsSync3(indexPath)) return next();
|
|
30600
|
-
return c.html(await
|
|
30860
|
+
return c.html(await readFile4(indexPath, "utf8"));
|
|
30601
30861
|
});
|
|
30602
30862
|
app.all("/apps/*", (c) => c.notFound());
|
|
30603
30863
|
}
|
|
30604
30864
|
if (deps.webRoot) {
|
|
30605
|
-
const root =
|
|
30865
|
+
const root = resolve2(deps.webRoot);
|
|
30606
30866
|
const indexPath = join3(root, "index.html");
|
|
30607
30867
|
if (existsSync3(indexPath)) {
|
|
30608
30868
|
app.use("*", serveStatic({ root }));
|
|
@@ -30610,7 +30870,7 @@ function createApp(deps) {
|
|
|
30610
30870
|
if (c.req.path.startsWith("/api/") || c.req.path.startsWith("/apps/")) {
|
|
30611
30871
|
return next();
|
|
30612
30872
|
}
|
|
30613
|
-
const html = await
|
|
30873
|
+
const html = await readFile4(indexPath, "utf8");
|
|
30614
30874
|
return c.html(html);
|
|
30615
30875
|
});
|
|
30616
30876
|
}
|
|
@@ -30762,8 +31022,8 @@ function printPairingPanel(config2, pairing, options = {}) {
|
|
|
30762
31022
|
}
|
|
30763
31023
|
async function waitForListen(server) {
|
|
30764
31024
|
if (server.listening) return;
|
|
30765
|
-
await new Promise((
|
|
30766
|
-
server.once("listening",
|
|
31025
|
+
await new Promise((resolve4, reject) => {
|
|
31026
|
+
server.once("listening", resolve4);
|
|
30767
31027
|
server.once("error", reject);
|
|
30768
31028
|
});
|
|
30769
31029
|
}
|
|
@@ -31037,7 +31297,7 @@ Start Oktoso on another port with: npx @amoshydra/oktoso --port ${config2.port <
|
|
|
31037
31297
|
}
|
|
31038
31298
|
createLogFile();
|
|
31039
31299
|
auth = new AuthService(config2.token);
|
|
31040
|
-
const stateFile = join4(
|
|
31300
|
+
const stateFile = join4(resolve3(config2.dataDir), "state.json");
|
|
31041
31301
|
const store = await JsonStateStore.open({ filePath: stateFile });
|
|
31042
31302
|
const eventBus = new EventBus(store, logger);
|
|
31043
31303
|
await eventBus.init();
|
|
@@ -31061,9 +31321,17 @@ Start Oktoso on another port with: npx @amoshydra/oktoso --port ${config2.port <
|
|
|
31061
31321
|
logger,
|
|
31062
31322
|
attention
|
|
31063
31323
|
);
|
|
31064
|
-
const
|
|
31065
|
-
|
|
31066
|
-
|
|
31324
|
+
const providerAdmin = new ProviderAdminService(
|
|
31325
|
+
registry2,
|
|
31326
|
+
sessions,
|
|
31327
|
+
attention,
|
|
31328
|
+
providerEvents,
|
|
31329
|
+
logger,
|
|
31330
|
+
options.configPath
|
|
31331
|
+
);
|
|
31332
|
+
const bundledRoot = dirname5(fileURLToPath(import.meta.url));
|
|
31333
|
+
const configuredWebRoot = config2.webRoot ? resolve3(config2.webRoot) : existsSync4(resolve3(bundledRoot, "web")) ? resolve3(bundledRoot, "web") : existsSync4(resolve3("dist/web")) ? resolve3("dist/web") : void 0;
|
|
31334
|
+
const configuredAppsRoot = config2.appsRoot ? resolve3(config2.appsRoot) : existsSync4(resolve3(bundledRoot, "apps")) ? resolve3(bundledRoot, "apps") : existsSync4(resolve3("dist/apps")) ? resolve3("dist/apps") : void 0;
|
|
31067
31335
|
const hubWebUrl = webUrl(config2);
|
|
31068
31336
|
const startedAt = Date.now();
|
|
31069
31337
|
const app = createApp({
|
|
@@ -31071,6 +31339,7 @@ Start Oktoso on another port with: npx @amoshydra/oktoso --port ${config2.port <
|
|
|
31071
31339
|
store,
|
|
31072
31340
|
eventBus,
|
|
31073
31341
|
registry: registry2,
|
|
31342
|
+
providerAdmin,
|
|
31074
31343
|
sessions,
|
|
31075
31344
|
attention,
|
|
31076
31345
|
drafts,
|
|
@@ -31201,7 +31470,7 @@ Start Oktoso on another port with: npx @amoshydra/oktoso --port ${config2.port <
|
|
|
31201
31470
|
host: info.address,
|
|
31202
31471
|
port: info.port,
|
|
31203
31472
|
providers: config2.providers.length,
|
|
31204
|
-
dataDir:
|
|
31473
|
+
dataDir: resolve3(config2.dataDir),
|
|
31205
31474
|
webRoot: configuredWebRoot ?? null
|
|
31206
31475
|
});
|
|
31207
31476
|
if (tuiEnabled && ui) {
|