@amoshydra/oktoso 0.1.5 → 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/README.md +10 -25
- 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 +360 -89
- 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;
|
|
@@ -29602,16 +29838,17 @@ function requestHost(c) {
|
|
|
29602
29838
|
}
|
|
29603
29839
|
function isSameOriginRequest(c) {
|
|
29604
29840
|
const origin = c.req.header("origin");
|
|
29841
|
+
const site = c.req.header("sec-fetch-site");
|
|
29605
29842
|
if (origin && origin !== "null") {
|
|
29606
29843
|
try {
|
|
29607
29844
|
const host = requestHost(c) ?? new URL(c.req.url).host;
|
|
29608
|
-
|
|
29845
|
+
if (new URL(origin).host === host) return true;
|
|
29609
29846
|
} catch {
|
|
29610
|
-
return false;
|
|
29611
29847
|
}
|
|
29848
|
+
return site === "same-origin" || site === "none";
|
|
29612
29849
|
}
|
|
29613
|
-
|
|
29614
|
-
return
|
|
29850
|
+
if (site) return site === "same-origin" || site === "none";
|
|
29851
|
+
return false;
|
|
29615
29852
|
}
|
|
29616
29853
|
|
|
29617
29854
|
// packages/hub/src/http/auth-cookie.ts
|
|
@@ -29956,12 +30193,13 @@ function registerPairRoutes(app, deps) {
|
|
|
29956
30193
|
});
|
|
29957
30194
|
app.post("/pair/:code", (c) => {
|
|
29958
30195
|
noStore2(c);
|
|
29959
|
-
if (
|
|
29960
|
-
|
|
29961
|
-
"
|
|
29962
|
-
|
|
29963
|
-
|
|
29964
|
-
|
|
30196
|
+
if (!isSameOriginRequest(c)) {
|
|
30197
|
+
c.get("logger").warn("pairing origin mismatch", {
|
|
30198
|
+
origin: c.req.header("origin") ?? null,
|
|
30199
|
+
host: requestHost(c) ?? null,
|
|
30200
|
+
forwardedHost: c.req.header("x-forwarded-host") ?? null,
|
|
30201
|
+
site: c.req.header("sec-fetch-site") ?? null
|
|
30202
|
+
});
|
|
29965
30203
|
}
|
|
29966
30204
|
const consumed = deps.auth.consumePairing(c.req.param("code"));
|
|
29967
30205
|
if (!consumed) {
|
|
@@ -29976,8 +30214,32 @@ function registerPairRoutes(app, deps) {
|
|
|
29976
30214
|
}
|
|
29977
30215
|
|
|
29978
30216
|
// packages/hub/src/http/routes/providers.ts
|
|
29979
|
-
function registerProviderRoutes(app, registry2) {
|
|
30217
|
+
function registerProviderRoutes(app, registry2, admin) {
|
|
29980
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
|
+
});
|
|
29981
30243
|
app.post("/api/v1/providers/:providerID/check", async (c) => {
|
|
29982
30244
|
const providerID = c.req.param("providerID");
|
|
29983
30245
|
if (!registry2.get(providerID)) throw notFound("Provider not found");
|
|
@@ -30556,7 +30818,7 @@ function createApp(deps) {
|
|
|
30556
30818
|
});
|
|
30557
30819
|
registerAuthRoutes(app, { auth, version: VERSION });
|
|
30558
30820
|
registerPairRoutes(app, { auth });
|
|
30559
|
-
registerProviderRoutes(app, deps.registry);
|
|
30821
|
+
registerProviderRoutes(app, deps.registry, deps.providerAdmin);
|
|
30560
30822
|
registerSessionRoutes(
|
|
30561
30823
|
app,
|
|
30562
30824
|
deps.sessions,
|
|
@@ -30576,7 +30838,7 @@ function createApp(deps) {
|
|
|
30576
30838
|
store: deps.store
|
|
30577
30839
|
});
|
|
30578
30840
|
if (deps.appsRoot) {
|
|
30579
|
-
const appsRoot =
|
|
30841
|
+
const appsRoot = resolve2(deps.appsRoot);
|
|
30580
30842
|
app.get(
|
|
30581
30843
|
"/apps/:app",
|
|
30582
30844
|
(c) => c.redirect(`/apps/${encodeURIComponent(c.req.param("app"))}/`, 308)
|
|
@@ -30595,12 +30857,12 @@ function createApp(deps) {
|
|
|
30595
30857
|
if (leaf.includes(".")) return next();
|
|
30596
30858
|
const indexPath = join3(appsRoot, appID, "index.html");
|
|
30597
30859
|
if (!existsSync3(indexPath)) return next();
|
|
30598
|
-
return c.html(await
|
|
30860
|
+
return c.html(await readFile4(indexPath, "utf8"));
|
|
30599
30861
|
});
|
|
30600
30862
|
app.all("/apps/*", (c) => c.notFound());
|
|
30601
30863
|
}
|
|
30602
30864
|
if (deps.webRoot) {
|
|
30603
|
-
const root =
|
|
30865
|
+
const root = resolve2(deps.webRoot);
|
|
30604
30866
|
const indexPath = join3(root, "index.html");
|
|
30605
30867
|
if (existsSync3(indexPath)) {
|
|
30606
30868
|
app.use("*", serveStatic({ root }));
|
|
@@ -30608,7 +30870,7 @@ function createApp(deps) {
|
|
|
30608
30870
|
if (c.req.path.startsWith("/api/") || c.req.path.startsWith("/apps/")) {
|
|
30609
30871
|
return next();
|
|
30610
30872
|
}
|
|
30611
|
-
const html = await
|
|
30873
|
+
const html = await readFile4(indexPath, "utf8");
|
|
30612
30874
|
return c.html(html);
|
|
30613
30875
|
});
|
|
30614
30876
|
}
|
|
@@ -30760,8 +31022,8 @@ function printPairingPanel(config2, pairing, options = {}) {
|
|
|
30760
31022
|
}
|
|
30761
31023
|
async function waitForListen(server) {
|
|
30762
31024
|
if (server.listening) return;
|
|
30763
|
-
await new Promise((
|
|
30764
|
-
server.once("listening",
|
|
31025
|
+
await new Promise((resolve4, reject) => {
|
|
31026
|
+
server.once("listening", resolve4);
|
|
30765
31027
|
server.once("error", reject);
|
|
30766
31028
|
});
|
|
30767
31029
|
}
|
|
@@ -31035,7 +31297,7 @@ Start Oktoso on another port with: npx @amoshydra/oktoso --port ${config2.port <
|
|
|
31035
31297
|
}
|
|
31036
31298
|
createLogFile();
|
|
31037
31299
|
auth = new AuthService(config2.token);
|
|
31038
|
-
const stateFile = join4(
|
|
31300
|
+
const stateFile = join4(resolve3(config2.dataDir), "state.json");
|
|
31039
31301
|
const store = await JsonStateStore.open({ filePath: stateFile });
|
|
31040
31302
|
const eventBus = new EventBus(store, logger);
|
|
31041
31303
|
await eventBus.init();
|
|
@@ -31059,9 +31321,17 @@ Start Oktoso on another port with: npx @amoshydra/oktoso --port ${config2.port <
|
|
|
31059
31321
|
logger,
|
|
31060
31322
|
attention
|
|
31061
31323
|
);
|
|
31062
|
-
const
|
|
31063
|
-
|
|
31064
|
-
|
|
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;
|
|
31065
31335
|
const hubWebUrl = webUrl(config2);
|
|
31066
31336
|
const startedAt = Date.now();
|
|
31067
31337
|
const app = createApp({
|
|
@@ -31069,6 +31339,7 @@ Start Oktoso on another port with: npx @amoshydra/oktoso --port ${config2.port <
|
|
|
31069
31339
|
store,
|
|
31070
31340
|
eventBus,
|
|
31071
31341
|
registry: registry2,
|
|
31342
|
+
providerAdmin,
|
|
31072
31343
|
sessions,
|
|
31073
31344
|
attention,
|
|
31074
31345
|
drafts,
|
|
@@ -31199,7 +31470,7 @@ Start Oktoso on another port with: npx @amoshydra/oktoso --port ${config2.port <
|
|
|
31199
31470
|
host: info.address,
|
|
31200
31471
|
port: info.port,
|
|
31201
31472
|
providers: config2.providers.length,
|
|
31202
|
-
dataDir:
|
|
31473
|
+
dataDir: resolve3(config2.dataDir),
|
|
31203
31474
|
webRoot: configuredWebRoot ?? null
|
|
31204
31475
|
});
|
|
31205
31476
|
if (tuiEnabled && ui) {
|