openmausbot 0.1.91 → 0.1.93
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/assets/DesktopViewer-CKNR3SEW.js +5 -0
- package/dist/assets/{abnfDiagram-O67JEVCF-yP-w8pRq.js → abnfDiagram-O67JEVCF-Ckrm1I1X.js} +1 -1
- package/dist/assets/{arc-DsivfZEh.js → arc-BBdBuCgB.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-CILw7lzT.js → architectureDiagram-NJMV4G6O-CDdA3Hjs.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-DEQUV3HF.js → blockDiagram-BEXU5L5S-AUrsx1YR.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-lSXCZoKA.js → c4Diagram-YGBWAQC7-BEqQ0Unh.js} +1 -1
- package/dist/assets/channel-BU94VpxE.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-QCD5bgWP.js → chunk-2Q5K7J3B-CEEuvd4C.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-C5-iZJuL.js → chunk-3FUC2YCW-DyANCiHl.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-cxsawMpY.js → chunk-5VM5RSS4-Dx_Slpw9.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-DV8tV8H7.js → chunk-GTNCS2PH-Dl7h7iiD.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-CJFZyfbW.js → chunk-GWA4HPMP-BLx07SPb.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-BigbMXco.js → chunk-JWPE2WC7-BMxDj3n7.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-oVxTfpDy.js → chunk-NETBCI7D-CN66PT4H.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-CH4BiroW.js → chunk-WEXAMYUT-6HwhsDKw.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-KEDa6gax.js → chunk-XXDRQBXY-gWnLt7SN.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-BiPjfqVv.js → classDiagram-v2-NBCMYWYE-CMS5l2ea.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-Caxew3OY.js → cose-bilkent-JH36ORCC-CV7sQwIG.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-B_Pui369.js → cynefin-OW5HDTMX-CSyl-7vl.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-X64ydIhP.js → cynefinDiagram-VND7K2PF-BCLKL6az.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-TqYCI1E1.js → dagre-6A5THRUB-DiaZs6S1.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-TeOt3rAQ.js → diagram-22UHCM2B-BeTtmt3I.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-D70BLhDA.js → diagram-3UASUU5V-CsdBG-QQ.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-BbQDLwUC.js → diagram-ATOU4E4O-BczvdbR1.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-ChNJZ1u-.js → diagram-CDSNMT55-DHTTTMhi.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-DMBl2APa.js → diagram-MLGK6HIB-RTjHyd8z.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-CEqDchOp.js → diagram-MPIPVDR6-BDenHEH_.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-BVDKRp_-.js → ebnfDiagram-ZINNZB2B-BSiRSbtL.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-CRkofO9B.js → elk-276RUBZZ-BCAElss-.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-DruOMh5e.js → erDiagram-OPXOYQCR-C4_1ZPJe.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-8GMQeB7t.js → flowDiagram-KWPJA3E3-Bcb4Z75S.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-ORE9jcsM.js → ganttDiagram-FUAMR5RP-BiPOzxzb.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-BmsedsWh.js → gitGraphDiagram-X574FWY7-B1wtF3ZS.js} +1 -1
- package/dist/assets/index-Biji-bfL.css +1 -0
- package/dist/assets/{index-DKNEM8Q6.js → index-BkYwilkG.js} +1 -1
- package/dist/assets/index-DZlhoES0.js +597 -0
- package/dist/assets/{infoDiagram-VRGFBTTK-E_p7AooJ.js → infoDiagram-VRGFBTTK-A19No71U.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-Y-B2mKda.js → ishikawaDiagram-OU5B5YK6-BQsqJ_cV.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-eA8RELcK.js → journeyDiagram-ZHPQQLJL-DAO0pVan.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-DqIL2e8e.js → kanban-definition-PNTS6WVX-NepmKEe2.js} +1 -1
- package/dist/assets/{linear-yC_29R6e.js → linear-bq1PTDTW.js} +1 -1
- package/dist/assets/{mermaid.core-VhEMkSND.js → mermaid.core-s4LTbQle.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-oJ7kyQZy.js → mindmap-definition-NLK3R4M7-LtdbgHFH.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-BmfYcY0O.js → pegDiagram-GJSIUBJH-tdbaCxiK.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-DKvgP7w9.js → pieDiagram-5QR66LMP-DvkbpW8l.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-BEKL3W0L.js → quadrantDiagram-O4NWA36T-Do4KaH-E.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-BEsiqR5s.js → railroadDiagram-XR7U4H2S-BQWULOAs.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP-DWBcWvAP.js → requirementDiagram-PLB6GJNP-w9kei1FU.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-rpZ2_O4s.js → sankeyDiagram-IPEJSGJF-D2gRy0e8.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-B99HQdJR.js → sequenceDiagram-PO4LG4MO-a2sO5_Eo.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-BjN0qT5T.js → sizeCapture-INFHLROL-CiPJmFEB.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-BbsVgGsC.js → stateDiagram-v2-GCMORJYK-DAkUUlL7.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-DG3gKkeR.js → swimlanes-2SLR337P-DANf2R8Y.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-CdHxG8aM.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-BTHOdtF_.js → timeline-definition-EJHVYXUP-BKpQmzrT.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-acLSLFwc.js → usecaseDiagram-POWQR4AR-BtI7qLN-.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-C5JzNX9G.js → vennDiagram-UO4OBE2U-gXJXrAG_.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-DL1-PrR_.js → wardleyDiagram-VNRHLVJA-GU8Po5-n.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-Bvr1477p.js → xychartDiagram-PMCCYNJV-BF4J30g6.js} +1 -1
- package/dist/index.html +2 -2
- package/dist/novnc-NOTICE.txt +118 -0
- package/dist-server/cloud-home-start.js +112 -14
- package/dist-server/companion/src/routes.js +24 -1
- package/dist-server/container-mcp.js +201 -16
- package/dist-server/drivers/agents-proxy.js +7 -4
- package/dist-server/enterprise/server/index.js +5 -1
- package/dist-server/index.js +10698 -4737
- package/dist-server/local-computer-proxy.js +133 -8
- package/dist-server/mcp-gate.js +16 -6
- package/dist-server/model-catalog/models-dev.snapshot.json +7299 -0
- package/dist-server/openmausbot.js +4656 -1258
- package/dist-server/pair-cli.js +4656 -1258
- package/dist-server/server/boat.js +5 -5
- package/dist-server/server/browser-engine.js +10 -1
- package/dist-server/server/browser-live.js +46 -2
- package/dist-server/server/browser-runtime.js +74 -8
- package/dist-server/server/browser-tool-shape.js +6 -4
- package/dist-server/server/chief-of-staff.js +1 -1
- package/dist-server/server/cloud-home-start.js +118 -26
- package/dist-server/server/cloud-home.js +39 -3
- package/dist-server/server/cloud-lending.js +74 -10
- package/dist-server/server/cloud-owner.js +196 -0
- package/dist-server/server/cloud-secrets-boot.js +6 -0
- package/dist-server/server/cloud-secrets.js +65 -0
- package/dist-server/server/computer-control.js +6 -0
- package/dist-server/server/config.js +187 -14
- package/dist-server/server/container-computer.js +46 -34
- package/dist-server/server/desktop-viewer-targets.js +26 -0
- package/dist-server/server/digest.js +12 -2
- package/dist-server/server/drivers/acp/core.js +339 -33
- package/dist-server/server/drivers/acp/cursor.js +13 -1
- package/dist-server/server/drivers/acp/opencode-go.js +614 -85
- package/dist-server/server/drivers/acp/quiet-status.js +257 -0
- package/dist-server/server/drivers/acp/qwen.js +18 -0
- package/dist-server/server/drivers/agents-catalog.js +6 -6
- package/dist-server/server/drivers/antigravity-acp.js +69 -11
- package/dist-server/server/drivers/antigravity-temp.js +360 -0
- package/dist-server/server/drivers/antigravity.js +21 -0
- package/dist-server/server/drivers/builtIn.js +2 -0
- package/dist-server/server/drivers/cerebras.js +108 -0
- package/dist-server/server/drivers/chat-mcp-tools.js +20 -3
- package/dist-server/server/drivers/chatgpt-plan-auth.js +481 -0
- package/dist-server/server/drivers/claude.js +329 -37
- package/dist-server/server/drivers/codex.js +228 -18
- package/dist-server/server/drivers/grok.js +2 -2
- package/dist-server/server/drivers/mistral.js +4 -3
- package/dist-server/server/drivers/openai-chat-protocol.js +4 -3
- package/dist-server/server/drivers/openai-chat.js +66 -4
- package/dist-server/server/drivers/openai-compat.js +73 -23
- package/dist-server/server/drivers/prompt-split.js +2 -0
- package/dist-server/server/engine-install.js +46 -12
- package/dist-server/server/env-path.js +21 -5
- package/dist-server/server/harness/registry.js +2 -2
- package/dist-server/server/index.js +1545 -238
- package/dist-server/server/lending-memory.js +479 -0
- package/dist-server/server/local-vm-idle.js +24 -6
- package/dist-server/server/local-vm-stop-reason.js +26 -0
- package/dist-server/server/managed-desktop.js +3 -0
- package/dist-server/server/mcp-bridge.js +5 -3
- package/dist-server/server/mcp-http.js +6 -3
- package/dist-server/server/mcp-oauth-discovery.js +133 -0
- package/dist-server/server/mcp-oauth-store.js +70 -0
- package/dist-server/server/mcp-oauth.js +550 -0
- package/dist-server/server/mcp-probe.js +14 -1
- package/dist-server/server/mcp-registry.js +43 -0
- package/dist-server/server/memory-upkeep.js +94 -84
- package/dist-server/server/model-catalog/catalog.js +374 -0
- package/dist-server/server/model-catalog/presets.js +176 -0
- package/dist-server/server/model-catalog/trim.js +219 -0
- package/dist-server/server/provider-key-check.js +5 -2
- package/dist-server/server/recent-work.js +1 -1
- package/dist-server/server/request-auth.js +23 -0
- package/dist-server/server/routes/antigravity-leftovers.js +45 -0
- package/dist-server/server/routes/bot-memory.js +31 -6
- package/dist-server/server/routes/desktop-viewer.js +238 -0
- package/dist-server/server/routine-requests.js +1 -1
- package/dist-server/server/routines.js +6 -3
- package/dist-server/server/sessions.js +56 -9
- package/dist-server/server/skills.js +25 -10
- package/dist-server/server/store.js +116 -122
- package/dist-server/server/thread-events.js +2 -0
- package/dist-server/server/thread-starters.js +33 -0
- package/dist-server/server/thread-title.js +46 -0
- package/dist-server/server/tts/fish.js +4 -5
- package/dist-server/server/tts/index.js +5 -3
- package/dist-server/server/vps-computer.js +80 -13
- package/dist-server/server/webhooks.js +18 -1
- package/dist-server/server/workspace-backup-policy.js +3 -0
- package/dist-server/server/workspace-backup.js +25 -0
- package/dist-server/server/workspace.js +66 -16
- package/dist-server/shared/approval-mode.js +1 -1
- package/dist-server/shared/redact.js +1 -0
- package/dist-server/shared/tool-name.js +22 -0
- package/dist-server/shared/workspace-backup-client.js +1 -0
- package/dist-server/src/lib/citations.js +177 -0
- package/dist-server/src/lib/composer-attachments.js +7 -1
- package/dist-server/vps-container-mcp.js +202 -16
- package/dist-server/workspace-backup.worker.js +87 -9
- package/enterprise/server/index.js +5 -1
- package/package.json +1 -1
- package/dist/assets/channel-DrfGMu7X.js +0 -1
- package/dist/assets/index-BlnCEcU6.js +0 -588
- package/dist/assets/index-DVxjOvID.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-BCZhxl7e.js +0 -8
|
@@ -10,10 +10,16 @@ import { codexToolSurfaceArgs } from "./codex-tool-surface.js";
|
|
|
10
10
|
//
|
|
11
11
|
// resumeCursor is the codex thread id; a later turn tries thread/resume
|
|
12
12
|
// and preserves that history or reports a failed resume.
|
|
13
|
-
import { existsSync } from "node:fs";
|
|
13
|
+
import { existsSync, mkdirSync } from "node:fs";
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
import { join } from "node:path";
|
|
14
16
|
import { homedir } from "node:os";
|
|
15
17
|
import { codexConfigMcpServerNames, mountedMcpServerName } from "./codex-mcp-names.js";
|
|
16
|
-
import { stripWorkspaceCredentialEnv } from "../config.js";
|
|
18
|
+
import { DATA_DIR, stripWorkspaceCredentialEnv } from "../config.js";
|
|
19
|
+
import { hostedWorkspaceConfigured } from "../enterprise.js";
|
|
20
|
+
import { cloudHomeConfigured } from "../cloud-home.js";
|
|
21
|
+
import { serverVersion } from "../environment.js";
|
|
22
|
+
import { ChatGptPlanAuthController } from "./chatgpt-plan-auth.js";
|
|
17
23
|
import { describeSpawnFailure, execCli, killCliTree, spawnCli } from "../procs.js";
|
|
18
24
|
import { isHarnessOwnedMcpEnvName } from "../mcp-registry.js";
|
|
19
25
|
import { newEventId, newId } from "../contracts.js";
|
|
@@ -34,6 +40,17 @@ import { parseProtocolAskQuestions, questionAnswersById, questionChoices } from
|
|
|
34
40
|
import { codexVersionBehind, readLatestCodexRelease } from "./codex-release.js";
|
|
35
41
|
export { decodeCodexSelection, readCodexModelCatalog, STATIC_CODEX_MODELS } from "./codex-catalog.js";
|
|
36
42
|
const DRIVER_KIND = "codex";
|
|
43
|
+
export function codexUserError(value, chatgptPlan) {
|
|
44
|
+
if (chatgptPlan && value.includes("subscription_sharing_usage_limit_exceeded")) {
|
|
45
|
+
return "subscription_sharing_usage_limit_exceeded: Your ChatGPT plan usage limit was reached. Manage usage in ChatGPT Settings, or explicitly choose another provider.";
|
|
46
|
+
}
|
|
47
|
+
if (value.includes("provider_not_configured")) {
|
|
48
|
+
return chatgptPlan
|
|
49
|
+
? "provider_not_configured: ChatGPT has not enabled this model for the selected account. Refresh models or reconnect ChatGPT plan in Settings. API billing will not be used."
|
|
50
|
+
: "provider_not_configured: This Codex account cannot use the selected route. For the new ChatGPT plan models, choose ChatGPT plan in Settings → Engines and Continue with ChatGPT, then select a model from that account.";
|
|
51
|
+
}
|
|
52
|
+
return value.slice(0, 400);
|
|
53
|
+
}
|
|
37
54
|
class CodexRpcError extends Error {
|
|
38
55
|
code;
|
|
39
56
|
constructor(error) {
|
|
@@ -79,12 +96,32 @@ async function codexReleaseUpdate(version, cli) {
|
|
|
79
96
|
}
|
|
80
97
|
function decodeConfig(raw) {
|
|
81
98
|
const o = (raw ?? {});
|
|
99
|
+
if (o.authMode !== undefined && o.authMode !== "chatgpt-plan")
|
|
100
|
+
throw new Error("Unknown Codex sign-in mode.");
|
|
101
|
+
if (o.authMode && o.managed)
|
|
102
|
+
throw new Error("ChatGPT plan and Company billing cannot be combined.");
|
|
82
103
|
return {
|
|
83
104
|
cli: typeof o.cli === "string" ? o.cli : "codex",
|
|
84
105
|
fullAuto: o.fullAuto === true,
|
|
106
|
+
...(o.authMode === "chatgpt-plan" ? { authMode: "chatgpt-plan" } : {}),
|
|
85
107
|
...(o.managed && typeof o.managed === "object" ? { managed: decodeManagedCodex(o.managed) } : {}),
|
|
86
108
|
};
|
|
87
109
|
}
|
|
110
|
+
export function chatgptPlanCodexArgs() {
|
|
111
|
+
return [
|
|
112
|
+
"-c", 'model_provider="openai_chatgpt_plan"',
|
|
113
|
+
"-c", 'model_providers.openai_chatgpt_plan.name="ChatGPT plan"',
|
|
114
|
+
"-c", 'model_providers.openai_chatgpt_plan.base_url="https://api.openai.com/v1"',
|
|
115
|
+
"-c", 'model_providers.openai_chatgpt_plan.env_key="OPENMAUSBOT_CHATGPT_TOKEN"',
|
|
116
|
+
"-c", 'model_providers.openai_chatgpt_plan.wire_api="responses"',
|
|
117
|
+
"-c", "model_providers.openai_chatgpt_plan.requires_openai_auth=false",
|
|
118
|
+
"-c", "model_providers.openai_chatgpt_plan.supports_websockets=false",
|
|
119
|
+
"-c", 'cli_auth_credentials_store="ephemeral"',
|
|
120
|
+
"-c", "features.tool_search=false",
|
|
121
|
+
"-c", "shell_environment_policy.ignore_default_excludes=false",
|
|
122
|
+
"-c", 'shell_environment_policy.exclude=["OPENMAUSBOT_CHATGPT_TOKEN"]',
|
|
123
|
+
];
|
|
124
|
+
}
|
|
88
125
|
function decodeManagedCodex(raw) {
|
|
89
126
|
const value = raw;
|
|
90
127
|
if (typeof value.url !== "string" || !Array.isArray(value.models) || !value.models.length || value.models.some(model => typeof model !== "string" || !/^[\w][\w./+-]*$/.test(model))) {
|
|
@@ -226,6 +263,16 @@ function mcpAppApprovalForm(params) {
|
|
|
226
263
|
const tool = boundedLabel(appName) ?? boundedLabel(request.serverName) ?? "app_access";
|
|
227
264
|
return { tool, summary: message, allowResult: { action: "accept", content } };
|
|
228
265
|
}
|
|
266
|
+
/** What a guest-driven turn's app-server starts with: the shell, unified
|
|
267
|
+
* exec and image reads off (codex-cli 0.159.0 `features`), web search too. */
|
|
268
|
+
export const GUEST_CONFINED_CODEX_ARGS = [
|
|
269
|
+
"-c", "features.shell_tool=false", "-c", "features.unified_exec=false", "-c", "features.view_image=false", "-c", 'web_search="disabled"',
|
|
270
|
+
];
|
|
271
|
+
/** Whether the effective config (config/read) shows the shell turned off. */
|
|
272
|
+
export function codexShellDisabled(config) {
|
|
273
|
+
const features = plainRecord(plainRecord(config)?.features);
|
|
274
|
+
return features?.shell_tool === false && features?.unified_exec === false && features?.view_image === false;
|
|
275
|
+
}
|
|
229
276
|
/** Codex persists these values on its native thread. Keep them explicit on
|
|
230
277
|
* start, resume, and every turn so switching modes cannot leave a more
|
|
231
278
|
* permissive sandbox/reviewer stuck to the next request. */
|
|
@@ -492,6 +539,12 @@ export const CodexDriver = {
|
|
|
492
539
|
defaultConfig: () => decodeConfig({}),
|
|
493
540
|
async create(input) {
|
|
494
541
|
const { instanceId, config } = input;
|
|
542
|
+
const plan = config.authMode === "chatgpt-plan";
|
|
543
|
+
const planUnavailable = plan && (hostedWorkspaceConfigured() || cloudHomeConfigured())
|
|
544
|
+
? "ChatGPT plan sign-in for hosted Pro requires OpenAI's hosted-app approval. Use a Company model or API key until that integration is approved; the local desktop flow cannot be used here."
|
|
545
|
+
: undefined;
|
|
546
|
+
// Provider homes are excluded from portable backups and Move to Cloud.
|
|
547
|
+
const planDirectory = join(DATA_DIR, "providers", "chatgpt-plan", createHash("sha256").update(instanceId).digest("hex"));
|
|
495
548
|
const childEnv = () => {
|
|
496
549
|
const env = {
|
|
497
550
|
...process.env,
|
|
@@ -505,13 +558,31 @@ export const CodexDriver = {
|
|
|
505
558
|
// The harness process may hold workspace credentials (xai/box/voice
|
|
506
559
|
// keys, env-injected at boot); none of them are this CLI's to see.
|
|
507
560
|
stripWorkspaceCredentialEnv(env);
|
|
561
|
+
delete env.OPENMAUSBOT_CHATGPT_TOKEN;
|
|
562
|
+
if (plan)
|
|
563
|
+
env.CODEX_HOME = join(planDirectory, "codex");
|
|
508
564
|
return env;
|
|
509
565
|
};
|
|
510
566
|
const catalogEnv = childEnv();
|
|
511
|
-
|
|
567
|
+
const planAuth = plan ? new ChatGptPlanAuthController({ directory: planDirectory }) : null;
|
|
568
|
+
let planGeneration = 0;
|
|
569
|
+
let planSigningOut = false;
|
|
570
|
+
let disposed = false;
|
|
571
|
+
let planWarning;
|
|
572
|
+
let models = plan ? { default: "", options: [] } : config.managed ? { default: config.managed.models[0], options: config.managed.models.map(id => ({ id, label: id })) } : STATIC_CODEX_MODELS;
|
|
512
573
|
const refreshModels = async () => {
|
|
513
574
|
if (config.managed)
|
|
514
575
|
return;
|
|
576
|
+
if (planAuth) {
|
|
577
|
+
const generation = planGeneration;
|
|
578
|
+
models = { default: "", options: [] };
|
|
579
|
+
if (!planUnavailable && !planSigningOut && !disposed) {
|
|
580
|
+
const catalog = await planAuth.models();
|
|
581
|
+
if (generation === planGeneration && !planSigningOut && !disposed)
|
|
582
|
+
models = catalog;
|
|
583
|
+
}
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
515
586
|
try {
|
|
516
587
|
const resolved = await readCodexModelCatalog(catalogEnv, fetch, config.cli);
|
|
517
588
|
if (resolved.options.length)
|
|
@@ -521,7 +592,16 @@ export const CodexDriver = {
|
|
|
521
592
|
// Keep the last usable catalog when a local provider is down.
|
|
522
593
|
}
|
|
523
594
|
};
|
|
524
|
-
|
|
595
|
+
// A revoked grant or a temporary catalog outage must leave the account
|
|
596
|
+
// reachable in Settings for reconnect; it must not become a shadow.
|
|
597
|
+
if (planAuth) {
|
|
598
|
+
try {
|
|
599
|
+
await refreshModels();
|
|
600
|
+
}
|
|
601
|
+
catch { /* Explicit refresh reports the error. */ }
|
|
602
|
+
}
|
|
603
|
+
else
|
|
604
|
+
await refreshModels();
|
|
525
605
|
const authentication = new CodexDeviceAuthController({
|
|
526
606
|
cli: config.cli,
|
|
527
607
|
environment: childEnv,
|
|
@@ -541,6 +621,27 @@ export const CodexDriver = {
|
|
|
541
621
|
createdAt: new Date().toISOString(),
|
|
542
622
|
});
|
|
543
623
|
const sendTurn = async (turn) => {
|
|
624
|
+
const generation = planGeneration;
|
|
625
|
+
const assertPlanCurrent = () => {
|
|
626
|
+
if (disposed)
|
|
627
|
+
throw new Error("This provider was removed. Select a connected account and send again.");
|
|
628
|
+
if (planAuth && (planSigningOut || generation !== planGeneration))
|
|
629
|
+
throw new Error("The ChatGPT account changed while this message was preparing. Select a connected account and send again.");
|
|
630
|
+
};
|
|
631
|
+
assertPlanCurrent();
|
|
632
|
+
if (planUnavailable)
|
|
633
|
+
throw new Error(planUnavailable);
|
|
634
|
+
const planToken = planAuth ? await planAuth.accessToken() : undefined;
|
|
635
|
+
assertPlanCurrent();
|
|
636
|
+
if (planAuth) {
|
|
637
|
+
if (!turn.model || !models.options.some(model => model.id === turn.model)) {
|
|
638
|
+
await refreshModels();
|
|
639
|
+
assertPlanCurrent();
|
|
640
|
+
if (!turn.model || !models.options.some(model => model.id === turn.model))
|
|
641
|
+
throw new Error("This model is not available to the selected ChatGPT plan. Refresh models and choose a listed model; API billing will not be used.");
|
|
642
|
+
}
|
|
643
|
+
mkdirSync(join(planDirectory, "codex"), { recursive: true, mode: 0o700 });
|
|
644
|
+
}
|
|
544
645
|
if (config.managed) {
|
|
545
646
|
// One blanket refusal hides which prerequisite broke; name it so the
|
|
546
647
|
// person can fix the actual gap instead of reconnecting blind.
|
|
@@ -587,7 +688,13 @@ export const CodexDriver = {
|
|
|
587
688
|
const retryScale = Number(process.env.FAKE_CODEX_RETRY_SCALE ?? "1");
|
|
588
689
|
const launchAttempt = async (attempt) => {
|
|
589
690
|
const env = childEnv();
|
|
590
|
-
|
|
691
|
+
if (planToken)
|
|
692
|
+
env.OPENMAUSBOT_CHATGPT_TOKEN = planToken;
|
|
693
|
+
const appServerArgs = ["app-server", ...(plan ? chatgptPlanCodexArgs() : config.managed ? managedCodexArgs(config.managed) : codexLocalProviderArgs(env, turn.model)), ...codexToolSurfaceArgs(),
|
|
694
|
+
// A guest-driven turn (SendTurnInput.guestConfined): no shell and
|
|
695
|
+
// no file reads, whatever the person's own config says (-c wins
|
|
696
|
+
// over config files). The turn also starts with no environment.
|
|
697
|
+
...(turn.guestConfined ? GUEST_CONFINED_CODEX_ARGS : [])];
|
|
591
698
|
if (turn.integrations?.composio) {
|
|
592
699
|
mountMcpServer(appServerArgs, env, "openmausbot_connectors", turn.integrations.composio);
|
|
593
700
|
}
|
|
@@ -746,8 +853,8 @@ export const CodexDriver = {
|
|
|
746
853
|
while (!state.settled && Date.now() < deadline) {
|
|
747
854
|
await new Promise((wake) => setTimeout(wake, 15));
|
|
748
855
|
}
|
|
749
|
-
|
|
750
|
-
|
|
856
|
+
// Protocol completion is not process termination. In particular,
|
|
857
|
+
// sign-out must wait until the child holding its token has exited.
|
|
751
858
|
}
|
|
752
859
|
const stopped = await terminate();
|
|
753
860
|
if (stopped)
|
|
@@ -960,6 +1067,24 @@ export const CodexDriver = {
|
|
|
960
1067
|
(params.additionalPermissions != null || params.networkApprovalContext != null)) || undefined,
|
|
961
1068
|
});
|
|
962
1069
|
};
|
|
1070
|
+
const seenReviews = new Set();
|
|
1071
|
+
let reviewWarning = false;
|
|
1072
|
+
let timedOutReview = false;
|
|
1073
|
+
const retryMode = approvalMode[0].toUpperCase() + approvalMode.slice(1);
|
|
1074
|
+
const reviewNotice = (status, action) => {
|
|
1075
|
+
if (status === "warning") {
|
|
1076
|
+
emit({ ...base(threadId, turnId), type: "runtime.error",
|
|
1077
|
+
message: `Codex automatic review reported a timeout. Check what ran before retrying. Retry stays ${retryMode}. Select Ask for human approval in approval settings.`,
|
|
1078
|
+
});
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
const command = action?.type === "command" ? commandSummary({ command: action.command }) : undefined;
|
|
1082
|
+
const target = command ? `: "${command.slice(0, 30)}"` : " for the requested action";
|
|
1083
|
+
const outcome = status === "timedOut" ? "timed out" : "denied";
|
|
1084
|
+
emit({ ...base(threadId, turnId), type: "runtime.error",
|
|
1085
|
+
message: `Codex automatic review ${outcome}${target}. Action did not run. Retry stays ${retryMode}. Select Ask for human approval in approval settings.`,
|
|
1086
|
+
});
|
|
1087
|
+
};
|
|
963
1088
|
const handleNotification = (msg) => {
|
|
964
1089
|
const p = msg.params ?? {};
|
|
965
1090
|
// An app-server also emits notifications for native helper threads.
|
|
@@ -997,10 +1122,32 @@ export const CodexDriver = {
|
|
|
997
1122
|
}
|
|
998
1123
|
const eventTurnId = msg.method === "turn/started" || msg.method === "turn/completed"
|
|
999
1124
|
? p.turn?.id : p.turnId;
|
|
1000
|
-
|
|
1125
|
+
// guardianWarning is thread-scoped in Codex 0.147; this child
|
|
1126
|
+
// process belongs to one app turn. Never admit a mismatched turnId.
|
|
1127
|
+
if (eventTurnId !== codexTurnId && !(msg.method === "guardianWarning" && eventTurnId === undefined))
|
|
1001
1128
|
return;
|
|
1002
1129
|
}
|
|
1003
1130
|
switch (msg.method) {
|
|
1131
|
+
case "guardianWarning":
|
|
1132
|
+
if (typeof p.message === "string" && /automatic approval review.*timed out/i.test(p.message))
|
|
1133
|
+
reviewWarning = true;
|
|
1134
|
+
break;
|
|
1135
|
+
case "item/autoApprovalReview/completed": {
|
|
1136
|
+
const status = p.review?.status;
|
|
1137
|
+
if (status !== "timedOut" && status !== "denied")
|
|
1138
|
+
break;
|
|
1139
|
+
// Completed reviews carry a reviewId. Without it distinct
|
|
1140
|
+
// failures cannot be separated from duplicate notifications.
|
|
1141
|
+
if (typeof p.reviewId !== "string" || !p.reviewId)
|
|
1142
|
+
break;
|
|
1143
|
+
if (!seenReviews.has(p.reviewId)) {
|
|
1144
|
+
seenReviews.add(p.reviewId);
|
|
1145
|
+
if (status === "timedOut")
|
|
1146
|
+
timedOutReview = true;
|
|
1147
|
+
reviewNotice(status, p.action);
|
|
1148
|
+
}
|
|
1149
|
+
break;
|
|
1150
|
+
}
|
|
1004
1151
|
// token-level chat text; the item/completed frame follows with the
|
|
1005
1152
|
// whole message, so its delta is only a fallback when none streamed
|
|
1006
1153
|
case "item/agentMessage/delta": {
|
|
@@ -1135,8 +1282,10 @@ export const CodexDriver = {
|
|
|
1135
1282
|
break;
|
|
1136
1283
|
}
|
|
1137
1284
|
case "turn/completed": {
|
|
1285
|
+
if (reviewWarning && !timedOutReview)
|
|
1286
|
+
reviewNotice("warning");
|
|
1138
1287
|
const t = p.turn ?? {};
|
|
1139
|
-
const message = typeof t.error?.message === "string" ? t.error.message
|
|
1288
|
+
const message = typeof t.error?.message === "string" ? codexUserError(t.error.message, plan) : "";
|
|
1140
1289
|
if (t.status !== "completed" && message && message !== state.lastError) {
|
|
1141
1290
|
state.lastError = message;
|
|
1142
1291
|
emit({ ...base(threadId, turnId), type: "runtime.error", message,
|
|
@@ -1153,7 +1302,7 @@ export const CodexDriver = {
|
|
|
1153
1302
|
{
|
|
1154
1303
|
const message = p.message ?? p.error?.message;
|
|
1155
1304
|
if (message) {
|
|
1156
|
-
state.lastError = String(message)
|
|
1305
|
+
state.lastError = codexUserError(String(message), plan);
|
|
1157
1306
|
emit({ ...base(threadId, turnId), type: "runtime.error", message: state.lastError });
|
|
1158
1307
|
}
|
|
1159
1308
|
}
|
|
@@ -1310,7 +1459,7 @@ export const CodexDriver = {
|
|
|
1310
1459
|
// nothing streamed yet, and never for auth/shape errors or interrupts
|
|
1311
1460
|
try {
|
|
1312
1461
|
await request("initialize", {
|
|
1313
|
-
clientInfo: { name: "openmausbot",
|
|
1462
|
+
clientInfo: { name: "openmausbot", title: "OpenMausBot", version: serverVersion() },
|
|
1314
1463
|
// Named permission profiles are an experimental app-server field in
|
|
1315
1464
|
// Codex 0.151. Negotiate them explicitly; older servers ignore this
|
|
1316
1465
|
// capability and remain on the legacy Custom fallback below.
|
|
@@ -1338,6 +1487,11 @@ export const CodexDriver = {
|
|
|
1338
1487
|
? "Codex rejected its configuration as invalid. Check ~/.codex/config.toml (or CODEX_HOME) for a broken entry, then retry."
|
|
1339
1488
|
: "Could not read Codex configuration; cannot safely update bot instructions. Retry after checking Codex.");
|
|
1340
1489
|
}
|
|
1490
|
+
// Proven before the turn starts: a Codex that did not take the
|
|
1491
|
+
// overrides runs nothing for a guest.
|
|
1492
|
+
if (turn.guestConfined && !codexShellDisabled(effectiveConfig)) {
|
|
1493
|
+
throw new Error(`This Codex could not turn its shell off, so it can't run this turn. Update Codex, or switch this bot to Claude.${turn.confinedWhy ? ` ${turn.confinedWhy}` : ""}`);
|
|
1494
|
+
}
|
|
1341
1495
|
// Only the stable half of the prompt belongs in the developer slot:
|
|
1342
1496
|
// it is the part that must survive compaction unchanged, and any
|
|
1343
1497
|
// change to it invalidates the provider's cached prefix. The volatile
|
|
@@ -1368,6 +1522,11 @@ export const CodexDriver = {
|
|
|
1368
1522
|
// Each turn launches a new app-server. Reassert current bot instructions
|
|
1369
1523
|
// on start AND resume so Codex owns their lifetime through compaction.
|
|
1370
1524
|
// Removed bot rules are cleared without dropping native configured rules.
|
|
1525
|
+
const selection = config.managed
|
|
1526
|
+
? { model: turn.model, modelProvider: "openmaus_company" }
|
|
1527
|
+
: config.authMode === "chatgpt-plan"
|
|
1528
|
+
? { model: turn.model, modelProvider: "openai_chatgpt_plan" }
|
|
1529
|
+
: decodeCodexSelection(turn.model);
|
|
1371
1530
|
const cursor = typeof turn.resumeCursor === "string" ? turn.resumeCursor : null;
|
|
1372
1531
|
let startedModel = null;
|
|
1373
1532
|
let resumedNativeThread = false;
|
|
@@ -1377,6 +1536,8 @@ export const CodexDriver = {
|
|
|
1377
1536
|
const resumeThread = () => request("thread/resume", {
|
|
1378
1537
|
threadId: cursor,
|
|
1379
1538
|
developerInstructions,
|
|
1539
|
+
model: selection.model,
|
|
1540
|
+
...(selection.modelProvider ? { modelProvider: selection.modelProvider } : {}),
|
|
1380
1541
|
...approvalParams.thread,
|
|
1381
1542
|
});
|
|
1382
1543
|
try {
|
|
@@ -1419,13 +1580,13 @@ export const CodexDriver = {
|
|
|
1419
1580
|
}
|
|
1420
1581
|
}
|
|
1421
1582
|
if (!codexThreadId) {
|
|
1422
|
-
const selection = config.managed ? { model: turn.model, modelProvider: "openmaus_company" } : decodeCodexSelection(turn.model);
|
|
1423
1583
|
const startThread = () => request("thread/start", {
|
|
1424
1584
|
developerInstructions,
|
|
1425
1585
|
cwd: turn.cwd ?? homedir(),
|
|
1426
1586
|
model: selection.model,
|
|
1427
1587
|
...(selection.modelProvider ? { modelProvider: selection.modelProvider } : {}),
|
|
1428
1588
|
...approvalParams.thread,
|
|
1589
|
+
...(turn.guestConfined ? { environments: [] } : {}),
|
|
1429
1590
|
ephemeral: false,
|
|
1430
1591
|
});
|
|
1431
1592
|
let started;
|
|
@@ -1464,6 +1625,10 @@ export const CodexDriver = {
|
|
|
1464
1625
|
threadId: codexThreadId,
|
|
1465
1626
|
input: turnInput,
|
|
1466
1627
|
...approvalParams.turn,
|
|
1628
|
+
// No environment: no exec_command, apply_patch or view_image, and
|
|
1629
|
+
// any call to them is refused (probed against codex-cli 0.159.0).
|
|
1630
|
+
// Without the experimental API the field is rejected, not ignored.
|
|
1631
|
+
...(turn.guestConfined ? { environments: [] } : {}),
|
|
1467
1632
|
// Spread, not `effort: turn.effort ?? null`. Probed against
|
|
1468
1633
|
// codex-cli 0.146.0: null is indistinguishable from an absent key
|
|
1469
1634
|
// — both leave the thread's current effort alone, emitting no
|
|
@@ -1492,7 +1657,7 @@ export const CodexDriver = {
|
|
|
1492
1657
|
}
|
|
1493
1658
|
catch (e) {
|
|
1494
1659
|
const failure = e instanceof Error ? e : { text: String(e) };
|
|
1495
|
-
const message = e instanceof Error ? e.message : String(e);
|
|
1660
|
+
const message = codexUserError(e instanceof Error ? e.message : String(e), plan);
|
|
1496
1661
|
const needsAuth = /(?:\b401\b|unauthorized|missing bearer|authentication required)/i.test(message);
|
|
1497
1662
|
const verdict = classifyError(failure);
|
|
1498
1663
|
// Three guards hold here: main's abandoned attempt never retries,
|
|
@@ -1541,12 +1706,17 @@ export const CodexDriver = {
|
|
|
1541
1706
|
return { turnId };
|
|
1542
1707
|
};
|
|
1543
1708
|
const snapshot = async () => {
|
|
1709
|
+
if (planUnavailable)
|
|
1710
|
+
return { state: "unavailable", authenticated: false, chatgptPlan: true, reason: planUnavailable, authenticationUnavailableReason: planUnavailable };
|
|
1544
1711
|
const env = childEnv();
|
|
1545
1712
|
const version = await new Promise((resolve) => {
|
|
1546
1713
|
execCli(config.cli, ["--version"], { timeout: 8000, env }, (err, stdout) => resolve(err ? null : stdout.trim()));
|
|
1547
1714
|
});
|
|
1548
1715
|
if (!version)
|
|
1549
|
-
return { state: "unavailable", reason: `\`${config.cli}\` CLI not found
|
|
1716
|
+
return { state: "unavailable", reason: `\`${config.cli}\` CLI not found`, ...(plan ? { chatgptPlan: true } : {}) };
|
|
1717
|
+
if (planAuth)
|
|
1718
|
+
return { state: "available", version, chatgptPlan: true, billing: "subscription", ...await planAuth.snapshot(), update: await codexReleaseUpdate(version, config.cli),
|
|
1719
|
+
...(planWarning ? { warning: { title: "Check ChatGPT connection", message: planWarning } } : {}) };
|
|
1550
1720
|
if (config.managed)
|
|
1551
1721
|
return { state: "available", version, authenticated: Boolean(input.environment.OPENMAUSBOT_COMPANY_API_KEY && input.environment.CODEX_HOME), billing: "metered" };
|
|
1552
1722
|
const authenticated = await new Promise((resolve) => {
|
|
@@ -1574,15 +1744,52 @@ export const CodexDriver = {
|
|
|
1574
1744
|
return models;
|
|
1575
1745
|
},
|
|
1576
1746
|
refreshModels,
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1747
|
+
...(plan ? { authenticationMethod: "browser-pkce" } : {}),
|
|
1748
|
+
startAuthentication: async () => {
|
|
1749
|
+
if (planUnavailable)
|
|
1750
|
+
throw new Error(planUnavailable);
|
|
1751
|
+
if (planSigningOut || disposed)
|
|
1752
|
+
throw new Error("This account is being disconnected. Wait before signing in again.");
|
|
1753
|
+
return (planAuth ?? authentication).start();
|
|
1754
|
+
},
|
|
1755
|
+
getAuthentication: async (flowId) => {
|
|
1756
|
+
const result = await (planAuth ?? authentication).get(flowId);
|
|
1757
|
+
if (planAuth && result.phase === "succeeded")
|
|
1758
|
+
planWarning = undefined;
|
|
1759
|
+
if (planAuth && result.phase === "succeeded" && !models.options.length)
|
|
1760
|
+
await refreshModels();
|
|
1761
|
+
return result;
|
|
1762
|
+
},
|
|
1763
|
+
cancelAuthentication: () => (planAuth ?? authentication).cancel(),
|
|
1764
|
+
signOut: async () => {
|
|
1765
|
+
if (!planAuth)
|
|
1766
|
+
return authentication.signOut();
|
|
1767
|
+
planSigningOut = true;
|
|
1768
|
+
planGeneration++;
|
|
1769
|
+
try {
|
|
1770
|
+
const stopped = await Promise.all([...active.values()].map(({ stop }) => stop()));
|
|
1771
|
+
if (stopped.some(value => !value))
|
|
1772
|
+
throw new Error("A ChatGPT task could not stop safely. Stop the task before signing out.");
|
|
1773
|
+
models = { default: "", options: [] };
|
|
1774
|
+
await planAuth.signOut();
|
|
1775
|
+
planWarning = undefined;
|
|
1776
|
+
}
|
|
1777
|
+
catch (error) {
|
|
1778
|
+
if (error.code !== "chatgpt_revocation_unconfirmed")
|
|
1779
|
+
throw error;
|
|
1780
|
+
planWarning = error.message;
|
|
1781
|
+
}
|
|
1782
|
+
finally {
|
|
1783
|
+
planSigningOut = false;
|
|
1784
|
+
}
|
|
1785
|
+
},
|
|
1581
1786
|
snapshot,
|
|
1582
1787
|
adapter: {
|
|
1583
1788
|
provider: DRIVER_KIND,
|
|
1584
1789
|
capabilities: {
|
|
1585
1790
|
sessionModelSwitch: "unsupported",
|
|
1791
|
+
// A guest's turn runs with no environment and the shell off (guestConfined).
|
|
1792
|
+
guestTurns: "confined",
|
|
1586
1793
|
queueing: true,
|
|
1587
1794
|
computerMcp: true,
|
|
1588
1795
|
localComputerMcp: true,
|
|
@@ -1622,7 +1829,10 @@ export const CodexDriver = {
|
|
|
1622
1829
|
},
|
|
1623
1830
|
},
|
|
1624
1831
|
dispose: async () => {
|
|
1832
|
+
disposed = true;
|
|
1833
|
+
planGeneration++;
|
|
1625
1834
|
await authentication.dispose();
|
|
1835
|
+
await planAuth?.dispose();
|
|
1626
1836
|
await Promise.all([...active.values()].map(({ stop }) => stop()));
|
|
1627
1837
|
listeners.clear();
|
|
1628
1838
|
},
|
|
@@ -38,8 +38,8 @@ export const GrokDriver = {
|
|
|
38
38
|
models: () => MODELS,
|
|
39
39
|
requestBody: (model, messages, stream) => ({ model, messages, stream }),
|
|
40
40
|
httpErrorLabel: "xAI",
|
|
41
|
-
missingKeyError: `
|
|
42
|
-
unavailableReason:
|
|
41
|
+
missingKeyError: `Save an xAI API key in Settings → API keys, or set ${config.apiKeyEnv}.`,
|
|
42
|
+
unavailableReason: "No xAI API key — open Settings → API keys.",
|
|
43
43
|
timeoutMs: 120_000,
|
|
44
44
|
retryScale: Number(process.env.FAKE_GROK_RETRY_SCALE ?? "1"),
|
|
45
45
|
generateModel: () => "grok-3-mini",
|
|
@@ -45,7 +45,8 @@ export const MistralDriver = {
|
|
|
45
45
|
models: DEFAULT_MODELS,
|
|
46
46
|
install: {
|
|
47
47
|
docsUrl: "https://console.mistral.ai/api-keys",
|
|
48
|
-
|
|
48
|
+
settings: "connections",
|
|
49
|
+
signInCommand: "Save a Mistral API key in Settings → API keys, or set MISTRAL_API_KEY on the server.",
|
|
49
50
|
},
|
|
50
51
|
decodeConfig,
|
|
51
52
|
defaultConfig: () => decodeConfig({}),
|
|
@@ -102,8 +103,8 @@ export const MistralDriver = {
|
|
|
102
103
|
tools: config.tools, models: () => catalog, refreshModels, contentText,
|
|
103
104
|
requestBody: (model, messages, stream) => ({ model, messages, stream }),
|
|
104
105
|
httpErrorLabel: "Mistral",
|
|
105
|
-
missingKeyError: "Save a Mistral API key in Settings →
|
|
106
|
-
unavailableReason: "No Mistral API key — open Settings →
|
|
106
|
+
missingKeyError: "Save a Mistral API key in Settings → API keys, or set MISTRAL_API_KEY.",
|
|
107
|
+
unavailableReason: "No Mistral API key — open Settings → API keys.",
|
|
107
108
|
timeoutMs: 180_000, billing: "metered", includeUsageInCompleted: true,
|
|
108
109
|
nativeLog: {
|
|
109
110
|
source: "mistral.chat.completions",
|
|
@@ -32,6 +32,7 @@ export class ChatToolCalls {
|
|
|
32
32
|
return;
|
|
33
33
|
if (!Array.isArray(value))
|
|
34
34
|
throw new ChatProtocolError("provider returned invalid tool_calls");
|
|
35
|
+
const omitted = (field) => field === undefined || (streaming && field === null);
|
|
35
36
|
for (const [position, raw] of value.entries()) {
|
|
36
37
|
const delta = object(raw);
|
|
37
38
|
const index = streaming ? delta?.index : position;
|
|
@@ -39,9 +40,9 @@ export class ChatToolCalls {
|
|
|
39
40
|
throw new ChatProtocolError("provider returned an invalid tool-call index");
|
|
40
41
|
}
|
|
41
42
|
const call = this.calls.get(Number(index)) ?? { id: "", type: "function", function: { name: "", arguments: "" } };
|
|
42
|
-
if (delta.type
|
|
43
|
+
if (!omitted(delta.type) && delta.type !== "function")
|
|
43
44
|
throw new ChatProtocolError("unsupported tool-call type");
|
|
44
|
-
if (delta.id
|
|
45
|
+
if (!omitted(delta.id)) {
|
|
45
46
|
if (typeof delta.id !== "string" || !delta.id || delta.id.length > 256 || (call.id && call.id !== delta.id)) {
|
|
46
47
|
throw new ChatProtocolError("provider changed or omitted a tool-call ID");
|
|
47
48
|
}
|
|
@@ -52,7 +53,7 @@ export class ChatToolCalls {
|
|
|
52
53
|
if (!fn)
|
|
53
54
|
throw new ChatProtocolError("provider returned an invalid tool-call function");
|
|
54
55
|
for (const field of ["name", "arguments"]) {
|
|
55
|
-
if (fn[field]
|
|
56
|
+
if (omitted(fn[field]))
|
|
56
57
|
continue;
|
|
57
58
|
if (typeof fn[field] !== "string")
|
|
58
59
|
throw new ChatProtocolError("provider returned invalid tool-call arguments or name");
|