copilot-hub 0.1.19 → 0.1.21
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 +3 -2
- package/apps/agent-engine/dist/config.js +58 -0
- package/apps/agent-engine/dist/index.js +90 -16
- package/apps/control-plane/dist/channels/codex-quota-cache.js +16 -0
- package/apps/control-plane/dist/channels/hub-model-utils.js +244 -24
- package/apps/control-plane/dist/channels/hub-ops-commands.js +631 -279
- package/apps/control-plane/dist/channels/telegram-channel.js +5 -7
- package/apps/control-plane/dist/config.js +58 -0
- package/apps/control-plane/dist/index.js +16 -0
- package/apps/control-plane/dist/test/hub-model-utils.test.js +110 -13
- package/package.json +3 -2
- package/packages/core/dist/agent-supervisor.d.ts +5 -0
- package/packages/core/dist/agent-supervisor.js +11 -0
- package/packages/core/dist/agent-supervisor.js.map +1 -1
- package/packages/core/dist/bot-manager.js +17 -1
- package/packages/core/dist/bot-manager.js.map +1 -1
- package/packages/core/dist/bot-runtime.d.ts +4 -0
- package/packages/core/dist/bot-runtime.js +5 -1
- package/packages/core/dist/bot-runtime.js.map +1 -1
- package/packages/core/dist/codex-app-client.d.ts +13 -2
- package/packages/core/dist/codex-app-client.js +51 -13
- package/packages/core/dist/codex-app-client.js.map +1 -1
- package/packages/core/dist/codex-app-utils.d.ts +6 -0
- package/packages/core/dist/codex-app-utils.js +49 -0
- package/packages/core/dist/codex-app-utils.js.map +1 -1
- package/packages/core/dist/codex-provider.d.ts +3 -1
- package/packages/core/dist/codex-provider.js +3 -1
- package/packages/core/dist/codex-provider.js.map +1 -1
- package/packages/core/dist/kernel-control-plane.d.ts +1 -0
- package/packages/core/dist/kernel-control-plane.js +132 -13
- package/packages/core/dist/kernel-control-plane.js.map +1 -1
- package/packages/core/dist/provider-factory.d.ts +2 -0
- package/packages/core/dist/provider-factory.js +3 -0
- package/packages/core/dist/provider-factory.js.map +1 -1
- package/packages/core/dist/provider-options.js +24 -17
- package/packages/core/dist/provider-options.js.map +1 -1
- package/packages/core/dist/state-store.d.ts +1 -0
- package/packages/core/dist/state-store.js +28 -2
- package/packages/core/dist/state-store.js.map +1 -1
- package/packages/core/dist/telegram-channel.d.ts +1 -0
- package/packages/core/dist/telegram-channel.js +3 -0
- package/packages/core/dist/telegram-channel.js.map +1 -1
- package/scripts/dist/cli.mjs +132 -203
- package/scripts/dist/codex-runtime.mjs +352 -0
- package/scripts/dist/codex-version.mjs +91 -0
- package/scripts/dist/configure.mjs +26 -49
- package/scripts/dist/daemon.mjs +58 -0
- package/scripts/src/cli.mts +166 -233
- package/scripts/src/codex-runtime.mts +499 -0
- package/scripts/src/codex-version.mts +114 -0
- package/scripts/src/configure.mts +30 -65
- package/scripts/src/daemon.mts +69 -0
- package/scripts/test/codex-version.test.mjs +21 -0
package/README.md
CHANGED
|
@@ -82,6 +82,7 @@ copilot-hub start
|
|
|
82
82
|
|
|
83
83
|
`start` runs guided setup automatically if required values are missing.
|
|
84
84
|
On interactive terminals, `start` can also offer OS-native service installation when it is not yet configured.
|
|
85
|
+
`start` and `update` also verify the supported Codex CLI range and can install the validated version automatically when needed.
|
|
85
86
|
|
|
86
87
|
## Quick start from source
|
|
87
88
|
|
|
@@ -165,9 +166,9 @@ Default values are already applied, and actions start from that agent workspace
|
|
|
165
166
|
## Startup troubleshooting
|
|
166
167
|
|
|
167
168
|
- If `npm run start` fails, first read the error and follow the suggested action.
|
|
168
|
-
- `npm run start` now
|
|
169
|
+
- `npm run start` now checks that Codex CLI is inside the supported range and can install the validated version automatically if missing or outside that range.
|
|
169
170
|
- For Codex login issues, run `codex login` (or the configured `CODEX_BIN`) and retry `npm run start`.
|
|
170
|
-
- If auto-install is skipped or unavailable, install Codex CLI with `npm install -g @openai/codex` or set `CODEX_BIN` in `.env
|
|
171
|
+
- If auto-install is skipped or unavailable, install Codex CLI with `npm install -g @openai/codex@0.113.0` or set `CODEX_BIN` in `.env` to a binary in the supported `0.113.x` range.
|
|
171
172
|
- If you are still stuck, ask your favorite LLM with the exact error output.
|
|
172
173
|
|
|
173
174
|
## Commands
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
3
4
|
import dotenv from "dotenv";
|
|
4
5
|
import { createWorkspaceBoundaryPolicy, assertWorkspaceAllowed, parseWorkspaceAllowedRoots, } from "@copilot-hub/core/workspace-policy";
|
|
5
6
|
import { parseTurnActivityTimeoutSetting } from "@copilot-hub/core/codex-app-utils";
|
|
@@ -124,6 +125,10 @@ function resolveCodexBin(rawValue) {
|
|
|
124
125
|
return value;
|
|
125
126
|
}
|
|
126
127
|
if (process.platform === "win32") {
|
|
128
|
+
const npmGlobalCodex = findWindowsNpmGlobalCodexBin();
|
|
129
|
+
if (npmGlobalCodex) {
|
|
130
|
+
return npmGlobalCodex;
|
|
131
|
+
}
|
|
127
132
|
const vscodeCodex = findVscodeCodexExe();
|
|
128
133
|
if (vscodeCodex) {
|
|
129
134
|
return vscodeCodex;
|
|
@@ -155,6 +160,59 @@ function findVscodeCodexExe() {
|
|
|
155
160
|
}
|
|
156
161
|
return null;
|
|
157
162
|
}
|
|
163
|
+
function findWindowsNpmGlobalCodexBin() {
|
|
164
|
+
if (process.platform !== "win32") {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const candidates = [];
|
|
168
|
+
const appData = String(process.env.APPDATA ?? "").trim();
|
|
169
|
+
if (appData) {
|
|
170
|
+
candidates.push(path.join(appData, "npm", "codex.cmd"));
|
|
171
|
+
candidates.push(path.join(appData, "npm", "codex.exe"));
|
|
172
|
+
candidates.push(path.join(appData, "npm", "codex"));
|
|
173
|
+
}
|
|
174
|
+
const npmPrefix = readNpmPrefix();
|
|
175
|
+
if (npmPrefix) {
|
|
176
|
+
candidates.push(path.join(npmPrefix, "codex.cmd"));
|
|
177
|
+
candidates.push(path.join(npmPrefix, "codex.exe"));
|
|
178
|
+
candidates.push(path.join(npmPrefix, "codex"));
|
|
179
|
+
}
|
|
180
|
+
for (const candidate of candidates) {
|
|
181
|
+
if (fs.existsSync(candidate)) {
|
|
182
|
+
return candidate;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
function readNpmPrefix() {
|
|
188
|
+
const result = spawnNpm(["config", "get", "prefix"]);
|
|
189
|
+
if (result.error || result.status !== 0) {
|
|
190
|
+
return "";
|
|
191
|
+
}
|
|
192
|
+
const value = String(result.stdout ?? "").trim();
|
|
193
|
+
if (!value || value.toLowerCase() === "undefined") {
|
|
194
|
+
return "";
|
|
195
|
+
}
|
|
196
|
+
return value;
|
|
197
|
+
}
|
|
198
|
+
function spawnNpm(args) {
|
|
199
|
+
if (process.platform === "win32") {
|
|
200
|
+
const comspec = process.env.ComSpec || "cmd.exe";
|
|
201
|
+
const commandLine = ["npm", ...args].join(" ");
|
|
202
|
+
return spawnSync(comspec, ["/d", "/s", "/c", commandLine], {
|
|
203
|
+
cwd: process.cwd(),
|
|
204
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
205
|
+
shell: false,
|
|
206
|
+
encoding: "utf8",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return spawnSync("npm", args, {
|
|
210
|
+
cwd: process.cwd(),
|
|
211
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
212
|
+
shell: false,
|
|
213
|
+
encoding: "utf8",
|
|
214
|
+
});
|
|
215
|
+
}
|
|
158
216
|
function normalizeThreadMode(value) {
|
|
159
217
|
const mode = String(value ?? "single")
|
|
160
218
|
.trim()
|
|
@@ -5,7 +5,9 @@ import { fileURLToPath } from "node:url";
|
|
|
5
5
|
import express from "express";
|
|
6
6
|
import { BotManager } from "@copilot-hub/core/bot-manager";
|
|
7
7
|
import { CodexAppClient } from "@copilot-hub/core/codex-app-client";
|
|
8
|
+
import { buildShellWrappedCommandLine, requiresShellWrappedSpawn, } from "@copilot-hub/core/codex-app-utils";
|
|
8
9
|
import { loadBotRegistry } from "@copilot-hub/core/bot-registry";
|
|
10
|
+
import { invalidateCodexQuotaUsageCache } from "@copilot-hub/core/telegram-channel";
|
|
9
11
|
import { config } from "./config.js";
|
|
10
12
|
import { InstanceLock } from "@copilot-hub/core/instance-lock";
|
|
11
13
|
import { KernelControlPlane } from "@copilot-hub/core/kernel-control-plane";
|
|
@@ -215,6 +217,7 @@ function buildApiApp({ botManager, controlPlane, registryFilePath, }) {
|
|
|
215
217
|
const refreshed = await refreshRunningBotProviders({
|
|
216
218
|
botManager: requireBotManager(),
|
|
217
219
|
});
|
|
220
|
+
invalidateCodexQuotaUsageCache();
|
|
218
221
|
res.json({
|
|
219
222
|
ok: true,
|
|
220
223
|
switched: true,
|
|
@@ -273,8 +276,18 @@ function buildApiApp({ botManager, controlPlane, registryFilePath, }) {
|
|
|
273
276
|
.trim()
|
|
274
277
|
.toLowerCase();
|
|
275
278
|
const hasModel = Object.prototype.hasOwnProperty.call(req.body ?? {}, "model");
|
|
279
|
+
const hasReasoningEffort = Object.prototype.hasOwnProperty.call(req.body ?? {}, "reasoningEffort");
|
|
280
|
+
const hasServiceTier = Object.prototype.hasOwnProperty.call(req.body ?? {}, "serviceTier");
|
|
276
281
|
const rawModel = req.body?.model;
|
|
282
|
+
const rawReasoningEffort = req.body?.reasoningEffort;
|
|
283
|
+
const rawServiceTier = req.body?.serviceTier;
|
|
277
284
|
const model = rawModel === null || rawModel === undefined ? null : String(rawModel).trim();
|
|
285
|
+
const reasoningEffort = rawReasoningEffort === null || rawReasoningEffort === undefined
|
|
286
|
+
? null
|
|
287
|
+
: String(rawReasoningEffort).trim().toLowerCase();
|
|
288
|
+
const serviceTier = rawServiceTier === null || rawServiceTier === undefined
|
|
289
|
+
? null
|
|
290
|
+
: String(rawServiceTier).trim().toLowerCase();
|
|
278
291
|
if (!sandboxMode) {
|
|
279
292
|
res.status(400).json({ error: "Field 'sandboxMode' is required." });
|
|
280
293
|
return;
|
|
@@ -291,6 +304,12 @@ function buildApiApp({ botManager, controlPlane, registryFilePath, }) {
|
|
|
291
304
|
if (hasModel) {
|
|
292
305
|
payload.model = model;
|
|
293
306
|
}
|
|
307
|
+
if (hasReasoningEffort) {
|
|
308
|
+
payload.reasoningEffort = reasoningEffort;
|
|
309
|
+
}
|
|
310
|
+
if (hasServiceTier) {
|
|
311
|
+
payload.serviceTier = serviceTier;
|
|
312
|
+
}
|
|
294
313
|
const result = await controlPlane.runSystemAction(CONTROL_ACTIONS.BOTS_SET_POLICY, payload);
|
|
295
314
|
res.json(result);
|
|
296
315
|
}));
|
|
@@ -476,13 +495,21 @@ function startCodexDeviceAuthSession() {
|
|
|
476
495
|
refreshedBots: [],
|
|
477
496
|
refreshFailures: [],
|
|
478
497
|
};
|
|
479
|
-
const child =
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
498
|
+
const child = requiresShellWrappedSpawn(codexBin)
|
|
499
|
+
? spawn(buildShellWrappedCommandLine(codexBin, ["login", "--device-auth"]), {
|
|
500
|
+
cwd: config.kernelRootPath,
|
|
501
|
+
shell: true,
|
|
502
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
503
|
+
windowsHide: true,
|
|
504
|
+
env: process.env,
|
|
505
|
+
})
|
|
506
|
+
: spawn(codexBin, ["login", "--device-auth"], {
|
|
507
|
+
cwd: config.kernelRootPath,
|
|
508
|
+
shell: false,
|
|
509
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
510
|
+
windowsHide: true,
|
|
511
|
+
env: process.env,
|
|
512
|
+
});
|
|
486
513
|
session.child = child;
|
|
487
514
|
codexDeviceAuthSession = session;
|
|
488
515
|
child.stdout.on("data", (chunk) => {
|
|
@@ -507,6 +534,7 @@ function startCodexDeviceAuthSession() {
|
|
|
507
534
|
botManager: requireBotManager(),
|
|
508
535
|
})
|
|
509
536
|
.then((refreshed) => {
|
|
537
|
+
invalidateCodexQuotaUsageCache();
|
|
510
538
|
session.refreshedBots = refreshed.refreshedBotIds;
|
|
511
539
|
session.refreshFailures = refreshed.failures;
|
|
512
540
|
session.status = "succeeded";
|
|
@@ -706,6 +734,8 @@ function normalizeModelCatalog(rawModels) {
|
|
|
706
734
|
displayName: String(entry?.displayName ?? model).trim() || model,
|
|
707
735
|
description: String(entry?.description ?? "").trim(),
|
|
708
736
|
isDefault: entry?.isDefault === true,
|
|
737
|
+
supportedReasoningEfforts: normalizeReasoningCatalog(entry?.supportedReasoningEfforts),
|
|
738
|
+
defaultReasoningEffort: normalizeReasoningEffortValue(entry?.defaultReasoningEffort),
|
|
709
739
|
});
|
|
710
740
|
}
|
|
711
741
|
return normalized.sort((a, b) => {
|
|
@@ -725,17 +755,58 @@ function looksLikeCodexApiKey(value) {
|
|
|
725
755
|
}
|
|
726
756
|
return key.startsWith("sk-");
|
|
727
757
|
}
|
|
758
|
+
function normalizeReasoningCatalog(value) {
|
|
759
|
+
const options = [];
|
|
760
|
+
const seen = new Set();
|
|
761
|
+
for (const entry of Array.isArray(value) ? value : []) {
|
|
762
|
+
const option = isRecord(entry) ? entry : {};
|
|
763
|
+
const reasoningEffort = normalizeReasoningEffortValue(option.reasoningEffort ?? option.effort ?? option.id);
|
|
764
|
+
if (!reasoningEffort || seen.has(reasoningEffort)) {
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
seen.add(reasoningEffort);
|
|
768
|
+
options.push({
|
|
769
|
+
reasoningEffort,
|
|
770
|
+
description: String(option.description ?? "").trim(),
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
return options;
|
|
774
|
+
}
|
|
775
|
+
function normalizeReasoningEffortValue(value) {
|
|
776
|
+
const normalized = String(value ?? "")
|
|
777
|
+
.trim()
|
|
778
|
+
.toLowerCase();
|
|
779
|
+
if (normalized === "none" ||
|
|
780
|
+
normalized === "minimal" ||
|
|
781
|
+
normalized === "low" ||
|
|
782
|
+
normalized === "medium" ||
|
|
783
|
+
normalized === "high" ||
|
|
784
|
+
normalized === "xhigh") {
|
|
785
|
+
return normalized;
|
|
786
|
+
}
|
|
787
|
+
return null;
|
|
788
|
+
}
|
|
728
789
|
function runCodexCommand(args, { inputText = "" } = {}) {
|
|
729
790
|
const codexBin = String(config.codexBin ?? "codex").trim() || "codex";
|
|
730
|
-
const result =
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
791
|
+
const result = requiresShellWrappedSpawn(codexBin)
|
|
792
|
+
? spawnSync(buildShellWrappedCommandLine(codexBin, args), {
|
|
793
|
+
cwd: config.kernelRootPath,
|
|
794
|
+
shell: true,
|
|
795
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
796
|
+
windowsHide: true,
|
|
797
|
+
encoding: "utf8",
|
|
798
|
+
input: inputText,
|
|
799
|
+
env: process.env,
|
|
800
|
+
})
|
|
801
|
+
: spawnSync(codexBin, args, {
|
|
802
|
+
cwd: config.kernelRootPath,
|
|
803
|
+
shell: false,
|
|
804
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
805
|
+
windowsHide: true,
|
|
806
|
+
encoding: "utf8",
|
|
807
|
+
input: inputText,
|
|
808
|
+
env: process.env,
|
|
809
|
+
});
|
|
739
810
|
if (result.error) {
|
|
740
811
|
return {
|
|
741
812
|
ok: false,
|
|
@@ -764,6 +835,9 @@ function formatCodexSpawnError(codexBin, error) {
|
|
|
764
835
|
if (code === "EPERM") {
|
|
765
836
|
return `Codex binary '${codexBin}' cannot be executed (EPERM).`;
|
|
766
837
|
}
|
|
838
|
+
if (code === "EINVAL" && requiresShellWrappedSpawn(codexBin)) {
|
|
839
|
+
return `Codex binary '${codexBin}' must be launched through the Windows shell.`;
|
|
840
|
+
}
|
|
767
841
|
const message = error instanceof Error ? error.message : String(error);
|
|
768
842
|
return `Failed to execute '${codexBin}': ${firstNonEmptyLine(message)}`;
|
|
769
843
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
let cachedCodexUsage = null;
|
|
2
|
+
export function readCachedCodexQuotaSnapshot(now) {
|
|
3
|
+
if (cachedCodexUsage && now < cachedCodexUsage.expiresAt) {
|
|
4
|
+
return cachedCodexUsage.snapshot;
|
|
5
|
+
}
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
export function writeCachedCodexQuotaSnapshot(snapshot, expiresAt) {
|
|
9
|
+
cachedCodexUsage = {
|
|
10
|
+
expiresAt,
|
|
11
|
+
snapshot,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function invalidateCodexQuotaUsageCache() {
|
|
15
|
+
cachedCodexUsage = null;
|
|
16
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const MODEL_INLINE_MAX =
|
|
1
|
+
const MODEL_INLINE_MAX = 24;
|
|
2
2
|
const MODEL_PATTERN = /^[A-Za-z0-9._:-]+$/;
|
|
3
3
|
export function parseSetModelCommand(text, botIdPattern) {
|
|
4
4
|
const tokens = String(text ?? "")
|
|
@@ -88,6 +88,30 @@ export function formatModelLabel(value) {
|
|
|
88
88
|
}
|
|
89
89
|
return model;
|
|
90
90
|
}
|
|
91
|
+
export function formatReasoningLabel(value) {
|
|
92
|
+
const reasoningEffort = normalizeReasoningEffortValue(value);
|
|
93
|
+
if (!reasoningEffort) {
|
|
94
|
+
return "Default";
|
|
95
|
+
}
|
|
96
|
+
switch (reasoningEffort) {
|
|
97
|
+
case "xhigh":
|
|
98
|
+
return "Extra High";
|
|
99
|
+
case "none":
|
|
100
|
+
return "None";
|
|
101
|
+
default:
|
|
102
|
+
return reasoningEffort.charAt(0).toUpperCase() + reasoningEffort.slice(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
export function formatFastModeLabel(value) {
|
|
106
|
+
const serviceTier = normalizeServiceTierValue(value);
|
|
107
|
+
if (serviceTier === "fast") {
|
|
108
|
+
return "Fast";
|
|
109
|
+
}
|
|
110
|
+
if (serviceTier === "flex") {
|
|
111
|
+
return "Flex (manual)";
|
|
112
|
+
}
|
|
113
|
+
return "Standard";
|
|
114
|
+
}
|
|
91
115
|
export function formatModelButtonText(label, selected) {
|
|
92
116
|
const text = String(label ?? "").trim() || "Model";
|
|
93
117
|
return selected ? `* ${text}` : text;
|
|
@@ -117,6 +141,8 @@ export function buildSessionModelOptions({ catalog, currentModel, inlineMax = MO
|
|
|
117
141
|
label: String(entry.displayName ?? model).trim() || model,
|
|
118
142
|
isDefault: entry.isDefault === true,
|
|
119
143
|
selected: normalizedCurrent === key,
|
|
144
|
+
supportedReasoningEfforts: normalizeReasoningCatalog(entry.supportedReasoningEfforts),
|
|
145
|
+
defaultReasoningEffort: normalizeReasoningEffortValue(entry.defaultReasoningEffort),
|
|
120
146
|
});
|
|
121
147
|
}
|
|
122
148
|
options.sort((a, b) => {
|
|
@@ -136,6 +162,47 @@ export function buildSessionModelOptions({ catalog, currentModel, inlineMax = MO
|
|
|
136
162
|
key: `k${index}`,
|
|
137
163
|
}));
|
|
138
164
|
}
|
|
165
|
+
export function buildReasoningOptionsForModel({ modelSelection, currentModel, currentReasoningEffort, }) {
|
|
166
|
+
if (!modelSelection.ok || !modelSelection.model) {
|
|
167
|
+
return [
|
|
168
|
+
{
|
|
169
|
+
key: "default",
|
|
170
|
+
reasoningEffort: null,
|
|
171
|
+
label: "Default",
|
|
172
|
+
description: "Use the default reasoning level for the resolved model.",
|
|
173
|
+
selected: true,
|
|
174
|
+
},
|
|
175
|
+
];
|
|
176
|
+
}
|
|
177
|
+
const selectedModel = String(modelSelection.model).trim().toLowerCase();
|
|
178
|
+
const normalizedCurrentModel = String(currentModel ?? "")
|
|
179
|
+
.trim()
|
|
180
|
+
.toLowerCase();
|
|
181
|
+
const normalizedCurrentReasoning = normalizeReasoningEffortValue(currentReasoningEffort);
|
|
182
|
+
const selectedReasoning = selectedModel === normalizedCurrentModel ? normalizedCurrentReasoning : null;
|
|
183
|
+
const options = [
|
|
184
|
+
{
|
|
185
|
+
key: "default",
|
|
186
|
+
reasoningEffort: null,
|
|
187
|
+
label: "Default",
|
|
188
|
+
description: modelSelection.defaultReasoningEffort !== null
|
|
189
|
+
? `Use ${formatReasoningLabel(modelSelection.defaultReasoningEffort)} for this model.`
|
|
190
|
+
: "Use the model default reasoning level.",
|
|
191
|
+
selected: selectedReasoning === null,
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
for (const entry of modelSelection.supportedReasoningEfforts) {
|
|
195
|
+
options.push({
|
|
196
|
+
key: `r${options.length - 1}`,
|
|
197
|
+
reasoningEffort: entry.reasoningEffort,
|
|
198
|
+
label: formatReasoningLabel(entry.reasoningEffort),
|
|
199
|
+
description: String(entry.description ?? "").trim() ||
|
|
200
|
+
`${formatReasoningLabel(entry.reasoningEffort)} reasoning effort.`,
|
|
201
|
+
selected: selectedReasoning === entry.reasoningEffort,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
return options;
|
|
205
|
+
}
|
|
139
206
|
export function resolveModelSelectionFromAction({ session, profileId, }) {
|
|
140
207
|
const target = String(profileId ?? "")
|
|
141
208
|
.trim()
|
|
@@ -143,15 +210,21 @@ export function resolveModelSelectionFromAction({ session, profileId, }) {
|
|
|
143
210
|
if (!target) {
|
|
144
211
|
return {
|
|
145
212
|
ok: false,
|
|
213
|
+
key: "",
|
|
146
214
|
model: null,
|
|
147
215
|
label: "",
|
|
216
|
+
supportedReasoningEfforts: [],
|
|
217
|
+
defaultReasoningEffort: null,
|
|
148
218
|
};
|
|
149
219
|
}
|
|
150
220
|
if (target === "auto") {
|
|
151
221
|
return {
|
|
152
222
|
ok: true,
|
|
223
|
+
key: "auto",
|
|
153
224
|
model: null,
|
|
154
225
|
label: "Auto (workspace default)",
|
|
226
|
+
supportedReasoningEfforts: [],
|
|
227
|
+
defaultReasoningEffort: null,
|
|
155
228
|
};
|
|
156
229
|
}
|
|
157
230
|
const options = Array.isArray(session?.modelOptions) ? session.modelOptions : [];
|
|
@@ -161,14 +234,47 @@ export function resolveModelSelectionFromAction({ session, profileId, }) {
|
|
|
161
234
|
if (!matched) {
|
|
162
235
|
return {
|
|
163
236
|
ok: false,
|
|
237
|
+
key: "",
|
|
164
238
|
model: null,
|
|
165
239
|
label: "",
|
|
240
|
+
supportedReasoningEfforts: [],
|
|
241
|
+
defaultReasoningEffort: null,
|
|
166
242
|
};
|
|
167
243
|
}
|
|
168
244
|
return {
|
|
169
245
|
ok: true,
|
|
246
|
+
key: matched.key,
|
|
170
247
|
model: String(matched.model ?? "").trim() || null,
|
|
171
248
|
label: String(matched.label ?? matched.model ?? "custom").trim(),
|
|
249
|
+
supportedReasoningEfforts: matched.supportedReasoningEfforts,
|
|
250
|
+
defaultReasoningEffort: matched.defaultReasoningEffort,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
export function resolveReasoningSelectionFromAction({ options, profileId, }) {
|
|
254
|
+
const target = String(profileId ?? "")
|
|
255
|
+
.trim()
|
|
256
|
+
.toLowerCase();
|
|
257
|
+
if (!target) {
|
|
258
|
+
return {
|
|
259
|
+
ok: false,
|
|
260
|
+
reasoningEffort: null,
|
|
261
|
+
label: "",
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const matched = options.find((entry) => String(entry?.key ?? "")
|
|
265
|
+
.trim()
|
|
266
|
+
.toLowerCase() === target);
|
|
267
|
+
if (!matched) {
|
|
268
|
+
return {
|
|
269
|
+
ok: false,
|
|
270
|
+
reasoningEffort: null,
|
|
271
|
+
label: "",
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
return {
|
|
275
|
+
ok: true,
|
|
276
|
+
reasoningEffort: matched.reasoningEffort,
|
|
277
|
+
label: matched.label,
|
|
172
278
|
};
|
|
173
279
|
}
|
|
174
280
|
export async function fetchCodexModelOptions(apiGet) {
|
|
@@ -193,7 +299,10 @@ export async function fetchCodexModelOptions(apiGet) {
|
|
|
193
299
|
models.push({
|
|
194
300
|
model,
|
|
195
301
|
displayName: String(entry.displayName ?? model).trim() || model,
|
|
302
|
+
description: String(entry.description ?? "").trim(),
|
|
196
303
|
isDefault: entry.isDefault === true,
|
|
304
|
+
supportedReasoningEfforts: normalizeReasoningCatalog(entry.supportedReasoningEfforts),
|
|
305
|
+
defaultReasoningEffort: normalizeReasoningEffortValue(entry.defaultReasoningEffort),
|
|
197
306
|
});
|
|
198
307
|
}
|
|
199
308
|
return {
|
|
@@ -226,9 +335,6 @@ export function resolveApprovalPolicy(value) {
|
|
|
226
335
|
}
|
|
227
336
|
return "never";
|
|
228
337
|
}
|
|
229
|
-
function isObject(value) {
|
|
230
|
-
return value !== null && typeof value === "object";
|
|
231
|
-
}
|
|
232
338
|
export function getBotPolicyState(botState) {
|
|
233
339
|
const provider = isObject(botState) && isObject(botState.provider) ? botState.provider : null;
|
|
234
340
|
const options = provider && isObject(provider.options) ? provider.options : {};
|
|
@@ -237,35 +343,68 @@ export function getBotPolicyState(botState) {
|
|
|
237
343
|
approvalPolicy: resolveApprovalPolicy(options.approvalPolicy),
|
|
238
344
|
};
|
|
239
345
|
}
|
|
240
|
-
export
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
346
|
+
export function getBotProviderSelection(botState) {
|
|
347
|
+
const provider = isObject(botState) && isObject(botState.provider) ? botState.provider : null;
|
|
348
|
+
const options = provider && isObject(provider.options) ? provider.options : {};
|
|
349
|
+
return {
|
|
350
|
+
model: normalizeModelValue(options.model),
|
|
351
|
+
reasoningEffort: normalizeReasoningEffortValue(options.reasoningEffort),
|
|
352
|
+
serviceTier: normalizeServiceTierValue(options.serviceTier),
|
|
353
|
+
};
|
|
247
354
|
}
|
|
248
|
-
export function
|
|
355
|
+
export function getRuntimeProviderSelection(runtime) {
|
|
249
356
|
if (!runtime || typeof runtime.getProviderOptions !== "function") {
|
|
250
|
-
return
|
|
357
|
+
return {
|
|
358
|
+
model: null,
|
|
359
|
+
reasoningEffort: null,
|
|
360
|
+
serviceTier: null,
|
|
361
|
+
};
|
|
251
362
|
}
|
|
252
363
|
const options = runtime.getProviderOptions();
|
|
253
|
-
|
|
254
|
-
|
|
364
|
+
const record = isObject(options) ? options : {};
|
|
365
|
+
return {
|
|
366
|
+
model: normalizeModelValue(record.model),
|
|
367
|
+
reasoningEffort: normalizeReasoningEffortValue(record.reasoningEffort),
|
|
368
|
+
serviceTier: normalizeServiceTierValue(record.serviceTier),
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
export async function applyBotProviderPolicy({ apiPost, botId, botState, patch, }) {
|
|
372
|
+
const policyState = getBotPolicyState(botState);
|
|
373
|
+
const payload = {
|
|
374
|
+
sandboxMode: policyState.sandboxMode,
|
|
375
|
+
approvalPolicy: policyState.approvalPolicy,
|
|
376
|
+
};
|
|
377
|
+
if (Object.prototype.hasOwnProperty.call(patch, "model")) {
|
|
378
|
+
payload.model = patch.model ?? null;
|
|
379
|
+
}
|
|
380
|
+
if (Object.prototype.hasOwnProperty.call(patch, "reasoningEffort")) {
|
|
381
|
+
payload.reasoningEffort = patch.reasoningEffort ?? null;
|
|
255
382
|
}
|
|
256
|
-
|
|
257
|
-
|
|
383
|
+
if (Object.prototype.hasOwnProperty.call(patch, "serviceTier")) {
|
|
384
|
+
payload.serviceTier = patch.serviceTier ?? null;
|
|
385
|
+
}
|
|
386
|
+
return apiPost(`/api/bots/${encodeURIComponent(botId)}/policy`, payload);
|
|
258
387
|
}
|
|
259
|
-
export async function
|
|
388
|
+
export async function applyRuntimeProviderPolicy({ runtime, patch, }) {
|
|
260
389
|
if (!runtime || typeof runtime.setProviderOptions !== "function") {
|
|
261
|
-
throw new Error("Hub
|
|
390
|
+
throw new Error("Hub provider update is not available on this runtime.");
|
|
391
|
+
}
|
|
392
|
+
const payload = {};
|
|
393
|
+
if (Object.prototype.hasOwnProperty.call(patch, "model")) {
|
|
394
|
+
payload.model = patch.model ?? null;
|
|
395
|
+
}
|
|
396
|
+
if (Object.prototype.hasOwnProperty.call(patch, "reasoningEffort")) {
|
|
397
|
+
payload.reasoningEffort = patch.reasoningEffort ?? null;
|
|
262
398
|
}
|
|
263
|
-
|
|
399
|
+
if (Object.prototype.hasOwnProperty.call(patch, "serviceTier")) {
|
|
400
|
+
payload.serviceTier = patch.serviceTier ?? null;
|
|
401
|
+
}
|
|
402
|
+
await runtime.setProviderOptions(payload);
|
|
264
403
|
}
|
|
265
404
|
export function resolveSharedModel(models) {
|
|
266
405
|
let normalizedModel;
|
|
267
406
|
for (const entry of Array.isArray(models) ? models : []) {
|
|
268
|
-
const nextModel =
|
|
407
|
+
const nextModel = normalizeModelValue(entry);
|
|
269
408
|
if (normalizedModel === undefined) {
|
|
270
409
|
normalizedModel = nextModel;
|
|
271
410
|
continue;
|
|
@@ -279,7 +418,41 @@ export function resolveSharedModel(models) {
|
|
|
279
418
|
model: normalizedModel ?? null,
|
|
280
419
|
};
|
|
281
420
|
}
|
|
282
|
-
export
|
|
421
|
+
export function resolveSharedReasoningEffort(values) {
|
|
422
|
+
let normalizedValue;
|
|
423
|
+
for (const entry of Array.isArray(values) ? values : []) {
|
|
424
|
+
const nextValue = normalizeReasoningEffortValue(entry);
|
|
425
|
+
if (normalizedValue === undefined) {
|
|
426
|
+
normalizedValue = nextValue;
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
if (normalizedValue !== nextValue) {
|
|
430
|
+
return { mode: "mixed" };
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
mode: "uniform",
|
|
435
|
+
reasoningEffort: normalizedValue ?? null,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
export function resolveSharedServiceTier(values) {
|
|
439
|
+
let normalizedValue;
|
|
440
|
+
for (const entry of Array.isArray(values) ? values : []) {
|
|
441
|
+
const nextValue = normalizeServiceTierValue(entry);
|
|
442
|
+
if (normalizedValue === undefined) {
|
|
443
|
+
normalizedValue = nextValue;
|
|
444
|
+
continue;
|
|
445
|
+
}
|
|
446
|
+
if (normalizedValue !== nextValue) {
|
|
447
|
+
return { mode: "mixed" };
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
return {
|
|
451
|
+
mode: "uniform",
|
|
452
|
+
serviceTier: normalizedValue ?? null,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
export async function applyProviderPolicyToBots({ apiPost, bots, patch, }) {
|
|
283
456
|
const updatedBotIds = [];
|
|
284
457
|
const failures = [];
|
|
285
458
|
for (const botState of Array.isArray(bots) ? bots : []) {
|
|
@@ -288,11 +461,11 @@ export async function applyModelPolicyToBots({ apiPost, bots, model, }) {
|
|
|
288
461
|
continue;
|
|
289
462
|
}
|
|
290
463
|
try {
|
|
291
|
-
await
|
|
464
|
+
await applyBotProviderPolicy({
|
|
292
465
|
apiPost,
|
|
293
466
|
botId,
|
|
294
467
|
botState,
|
|
295
|
-
|
|
468
|
+
patch,
|
|
296
469
|
});
|
|
297
470
|
updatedBotIds.push(botId);
|
|
298
471
|
}
|
|
@@ -308,6 +481,53 @@ export async function applyModelPolicyToBots({ apiPost, bots, model, }) {
|
|
|
308
481
|
failures,
|
|
309
482
|
};
|
|
310
483
|
}
|
|
484
|
+
function isObject(value) {
|
|
485
|
+
return value !== null && typeof value === "object";
|
|
486
|
+
}
|
|
487
|
+
function normalizeModelValue(value) {
|
|
488
|
+
const normalized = String(value ?? "").trim();
|
|
489
|
+
return normalized || null;
|
|
490
|
+
}
|
|
491
|
+
function normalizeReasoningCatalog(value) {
|
|
492
|
+
const options = [];
|
|
493
|
+
const seen = new Set();
|
|
494
|
+
for (const entry of Array.isArray(value) ? value : []) {
|
|
495
|
+
const option = isObject(entry) ? entry : {};
|
|
496
|
+
const reasoningEffort = normalizeReasoningEffortValue(option.reasoningEffort ?? option.effort ?? option.id);
|
|
497
|
+
if (!reasoningEffort || seen.has(reasoningEffort)) {
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
seen.add(reasoningEffort);
|
|
501
|
+
options.push({
|
|
502
|
+
reasoningEffort,
|
|
503
|
+
description: String(option.description ?? "").trim(),
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
return options;
|
|
507
|
+
}
|
|
508
|
+
function normalizeReasoningEffortValue(value) {
|
|
509
|
+
const normalized = String(value ?? "")
|
|
510
|
+
.trim()
|
|
511
|
+
.toLowerCase();
|
|
512
|
+
if (normalized === "none" ||
|
|
513
|
+
normalized === "minimal" ||
|
|
514
|
+
normalized === "low" ||
|
|
515
|
+
normalized === "medium" ||
|
|
516
|
+
normalized === "high" ||
|
|
517
|
+
normalized === "xhigh") {
|
|
518
|
+
return normalized;
|
|
519
|
+
}
|
|
520
|
+
return null;
|
|
521
|
+
}
|
|
522
|
+
function normalizeServiceTierValue(value) {
|
|
523
|
+
const normalized = String(value ?? "")
|
|
524
|
+
.trim()
|
|
525
|
+
.toLowerCase();
|
|
526
|
+
if (normalized === "fast" || normalized === "flex") {
|
|
527
|
+
return normalized;
|
|
528
|
+
}
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
311
531
|
function sanitizeError(error) {
|
|
312
532
|
const raw = error instanceof Error ? error.message : String(error);
|
|
313
533
|
return raw.split(/\r?\n/).slice(0, 6).join("\n");
|