@loicngr/kobo 2.1.28 → 2.1.30
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/AGENTS.md +60 -11
- package/CHANGELOG.md +20 -1
- package/CONFIGURATION.md +140 -35
- package/CONTRIBUTING.md +2 -2
- package/DESIGN.md +1 -1
- package/README.md +12 -9
- package/SECURITY.md +7 -2
- package/dist/server/db/memory-schema.js +5 -5
- package/dist/server/db/migrations.js +2 -0
- package/dist/server/db/mistral-memory-migration.js +40 -0
- package/dist/server/routes/engines.js +14 -0
- package/dist/server/routes/environment.js +1 -1
- package/dist/server/routes/health.js +14 -3
- package/dist/server/routes/settings.js +7 -1
- package/dist/server/routes/workspaces.js +116 -9
- package/dist/server/services/agent/engines/claude-code/capabilities.js +2 -0
- package/dist/server/services/agent/engines/claude-code/engine.js +3 -0
- package/dist/server/services/agent/engines/claude-code/resolve-binary.js +1 -1
- package/dist/server/services/agent/engines/codex/capabilities.js +2 -0
- package/dist/server/services/agent/engines/codex/engine.js +3 -0
- package/dist/server/services/agent/engines/configuration.js +14 -0
- package/dist/server/services/agent/engines/mistral-vibe/capabilities.js +13 -0
- package/dist/server/services/agent/engines/mistral-vibe/client.js +64 -0
- package/dist/server/services/agent/engines/mistral-vibe/configuration.js +76 -0
- package/dist/server/services/agent/engines/mistral-vibe/engine.js +361 -0
- package/dist/server/services/agent/engines/mistral-vibe/event-mapper.js +297 -0
- package/dist/server/services/agent/engines/mistral-vibe/options-builder.js +35 -0
- package/dist/server/services/agent/engines/mistral-vibe/server-requests.js +278 -0
- package/dist/server/services/agent/engines/mistral-vibe/spawn.js +176 -0
- package/dist/server/services/agent/engines/registry.js +2 -0
- package/dist/server/services/agent/engines/types.js +6 -0
- package/dist/server/services/agent/orchestrator.js +88 -25
- package/dist/server/services/agent/session-controller.js +5 -0
- package/dist/server/services/auto-loop-final-review-service.js +10 -3
- package/dist/server/services/dev-server-service.js +68 -4
- package/dist/server/services/environment-check-service.js +9 -3
- package/dist/server/services/memory-service.js +1 -1
- package/dist/server/services/mistral-options-service.js +145 -0
- package/dist/server/services/pr-watcher-service.js +40 -11
- package/dist/server/services/review-service.js +33 -9
- package/dist/server/services/session-handoff-service.js +28 -2
- package/dist/server/services/settings-defaults.js +1 -0
- package/dist/server/services/settings-service.js +19 -1
- package/dist/server/services/workspace-creation-mcp-service.js +50 -8
- package/dist/server/services/workspace-dialogue-service.js +2 -0
- package/dist/server/services/workspace-message-service.js +19 -1
- package/dist/server/services/workspace-permission-policy-service.js +24 -0
- package/dist/server/utils/bounded-process.js +1 -1
- package/dist/shared/launch-instructions.js +18 -7
- package/dist/shared/memory.js +1 -1
- package/dist/shared/mistral-models.js +9 -0
- package/dist/shared/mistral-options.js +1 -0
- package/dist/shared/question-selections.js +16 -0
- package/dist/shared/workspace-creation-tools.js +8 -2
- package/dist/shared/workspace-dialogue-tools.js +15 -4
- package/docs/deployment-vps.md +10 -3
- package/docs/getting-started.md +4 -3
- package/docs/troubleshooting.md +2 -2
- package/package.json +20 -14
- package/src/client/dist/pwa/assets/ActivityFeed-C5stmHXP.js +2 -0
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-CIDSX32d.css +1 -0
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-DG32W_0G.js +1 -0
- package/src/client/dist/pwa/assets/{ChangelogPage-CAAUEn5X.js → ChangelogPage-_vItY8vA.js} +1 -1
- package/src/client/dist/pwa/assets/{ClosePopup-CRV2hxHS.js → ClosePopup-rU8Bm-mC.js} +1 -1
- package/src/client/dist/pwa/assets/{ComparisonPanel-BCb7vB5E.js → ComparisonPanel-B3BMzQH0.js} +2 -2
- package/src/client/dist/pwa/assets/CreatePage-FlGrfmjX.js +2 -0
- package/src/client/dist/pwa/assets/CreatePage-oh8ihVC4.css +1 -0
- package/src/client/dist/pwa/assets/{DashboardPage-XE52EABP.js → DashboardPage-lJGwWQYQ.js} +1 -1
- package/src/client/dist/pwa/assets/DiffViewer-DIaQH3lA.js +8 -0
- package/src/client/dist/pwa/assets/{DocumentsPanel-L4pbQfz1.js → DocumentsPanel-Ct-nm4fX.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-DiOJm5he.js → DrawerToggleButton-M5qv4Io-.js} +1 -1
- package/src/client/dist/pwa/assets/GitPanel-BaHCgb3p.js +3 -0
- package/src/client/dist/pwa/assets/GitPanel-C8vw2qvW.css +1 -0
- package/src/client/dist/pwa/assets/HealthPage-DR3wa6iD.js +1 -0
- package/src/client/dist/pwa/assets/HealthPage-Dbn4J_DT.css +1 -0
- package/src/client/dist/pwa/assets/{MainLayout-DoAEgANU.css → MainLayout-CHi8qP1D.css} +1 -1
- package/src/client/dist/pwa/assets/MainLayout-WJabICsA.js +2 -0
- package/src/client/dist/pwa/assets/{MemoryPanel-DtEvh_b7.js → MemoryPanel-CRpWdfc0.js} +1 -1
- package/src/client/dist/pwa/assets/MemoryProposalList-DGz3lDbL.js +1 -0
- package/src/client/dist/pwa/assets/{QBadge-CW3Lw-kL.js → QBadge-DiwbRpte.js} +1 -1
- package/src/client/dist/pwa/assets/{QBanner-BT2ZemYu.js → QBanner-Cw89HSjk.js} +1 -1
- package/src/client/dist/pwa/assets/QBtn-kfhHFy7Q.js +1 -0
- package/src/client/dist/pwa/assets/{QBtnDropdown-CWPIugs8.js → QBtnDropdown-tt8rUNNR.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnGroup-CnM4vSJA.js → QBtnGroup-Bioxjp9q.js} +1 -1
- package/src/client/dist/pwa/assets/{QBtnToggle-DccOOtbh.js → QBtnToggle-Bod-x9im.js} +1 -1
- package/src/client/dist/pwa/assets/{QChip-Be16M4Ig.js → QChip-DvhtPv_L.js} +1 -1
- package/src/client/dist/pwa/assets/{QExpansionItem-D-EGL2b3.js → QExpansionItem-BkrI3UPL.js} +1 -1
- package/src/client/dist/pwa/assets/{QItemSection-MdJ1_ZK7.js → QItemSection-ahqRK4JF.js} +1 -1
- package/src/client/dist/pwa/assets/{QList-Cx1UfaUm.js → QList-CAUl0v66.js} +1 -1
- package/src/client/dist/pwa/assets/{QMarkupTable-UhP7fVUp.js → QMarkupTable-B4LLAly-.js} +1 -1
- package/src/client/dist/pwa/assets/{QPage-CqkGHokx.js → QPage-DIy_mRoT.js} +1 -1
- package/src/client/dist/pwa/assets/{QScrollArea-BMrqK-xd.js → QScrollArea-BvgnY-oA.js} +1 -1
- package/src/client/dist/pwa/assets/QSelect-BtZB1wrB.js +1 -0
- package/src/client/dist/pwa/assets/{QSpinnerDots-xdWyaE1i.js → QSpinnerDots-Bea6hAuJ.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-Bk8dLUtU.js → QTooltip-CDH6p745.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-CuxmxCKi.js → SchedulePanel-C2rWlh7l.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-DKnQPUqY.js → SearchPage-BFd4NQwJ.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-B4rmd2WX.css → SettingsPage-Bm0TcarK.css} +1 -1
- package/src/client/dist/pwa/assets/SettingsPage-DDQCgnjL.js +9 -0
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-WhQyX8fn.js → SplitWorkspacePage-DKCtOZO4.js} +1 -1
- package/src/client/dist/pwa/assets/TerminalPanel-BE_MSZF0.js +16 -0
- package/src/client/dist/pwa/assets/{ToolCallItem-DYubi-J0.js → ToolCallItem-BEYaOtgh.js} +4 -4
- package/src/client/dist/pwa/assets/{TouchPan-DDotyYLN.js → TouchPan-DeYoOjX5.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-kAnEMcy2.js → TourReplayButton-D5lMwIKt.js} +1 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-BHEAsIoo.js +1 -0
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-v7ein__H.css +1 -0
- package/src/client/dist/pwa/assets/WorkspacePage-5_wsJPaJ.js +4 -0
- package/src/client/dist/pwa/assets/WorkspacePage-CtUXz0Cq.css +1 -0
- package/src/client/dist/pwa/assets/{browser-DVYLH8vl.js → browser-ogy_9LVv.js} +2 -2
- package/src/client/dist/pwa/assets/build-path-tree-CRXxaYkA.js +1 -0
- package/src/client/dist/pwa/assets/custom-sounds-iLTkCd_i.js +1 -0
- package/src/client/dist/pwa/assets/de-BqDU7iZH.js +17 -0
- package/src/client/dist/pwa/assets/detached-fullscreen-CHqMxB-8.js +1 -0
- package/src/client/dist/pwa/assets/dom-CRPVbmiy.js +2 -0
- package/src/client/dist/pwa/assets/ecl-94qehhXe.js +1 -0
- package/src/client/dist/pwa/assets/{editor.api-BC38eXAS.js → editor.api-Dx6TbfZG.js} +9 -9
- package/src/client/dist/pwa/assets/editor.worker-xrHNhPSq.js +33 -0
- package/src/client/dist/pwa/assets/efforts-DnJh2fW_.js +9 -0
- package/src/client/dist/pwa/assets/es-D4VMXQRk.js +17 -0
- package/src/client/dist/pwa/assets/expand-template-PUEVWny3.js +1 -0
- package/src/client/dist/pwa/assets/{format-OwxXBnDJ.js → format-yf9XM3dV.js} +48 -48
- package/src/client/dist/pwa/assets/{formatters-YgaCZcwJ.js → formatters-D0sXu9Oe.js} +1 -1
- package/src/client/dist/pwa/assets/fr-DIyaCFB3.js +17 -0
- package/src/client/dist/pwa/assets/{freemarker2-l7R42fI2.js → freemarker2-l_xIHxRf.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-DFmPrgjH.js → handlebars-CYgq99yQ.js} +1 -1
- package/src/client/dist/pwa/assets/{html-CERIAlPM.js → html-C28GatN_.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-2AmHIUy0.js +1 -0
- package/src/client/dist/pwa/assets/index-C0MApoXN.css +1 -0
- package/src/client/dist/pwa/assets/index-MMYMztJ_.js +20 -0
- package/src/client/dist/pwa/assets/it-CF6_4OS0.js +17 -0
- package/src/client/dist/pwa/assets/{javascript-BBYncplF.js → javascript-YCSueyyk.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-C1HqYyYU.js → liquid-CUBn90Gs.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-E74dsP22.js → mdx-C5WOHShJ.js} +1 -1
- package/src/client/dist/pwa/assets/monaco-contributions-DYwqbh7m.js +53 -0
- package/src/client/dist/pwa/assets/{monaco.contribution-DYdn9CwR.js → monaco.contribution-CoFzrPQl.js} +2 -2
- package/src/client/dist/pwa/assets/network-auth-BpJ6Noax.js +1 -0
- package/src/client/dist/pwa/assets/notify-theme-NPq4-Tup.js +1 -0
- package/src/client/dist/pwa/assets/permissionModes-Bbl901Hx.js +36 -0
- package/src/client/dist/pwa/assets/{private.use-form-DY1BkePL.js → private.use-form-BcspuzM9.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-_snXWYUp.js → project-color-gNyhKJlV.js} +1 -1
- package/src/client/dist/pwa/assets/{python-DNAAGBcX.js → python-lhKUZdPj.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-CB3g5p_T.js → razor-Chi44rEL.js} +1 -1
- package/src/client/dist/pwa/assets/reactivity.esm-bundler-Dplx1ILy.js +1 -0
- package/src/client/dist/pwa/assets/render-chat-markdown-C9ihAYBt.js +67 -0
- package/src/client/dist/pwa/assets/runtime-core.esm-bundler-BAUgNzMK.js +1 -0
- package/src/client/dist/pwa/assets/{typescript-0kpz8bqP.js → typescript-CU2TXVm6.js} +1 -1
- package/src/client/dist/pwa/assets/{use-checkbox-v4aAbE6K.js → use-checkbox-DUxgXR4e.js} +1 -1
- package/src/client/dist/pwa/assets/{use-id-BrdYXce0.js → use-id-B7ieAx2m.js} +1 -1
- package/src/client/dist/pwa/assets/use-is-mobile-CHmaewI1.js +1 -0
- package/src/client/dist/pwa/assets/{use-search-index-status-D38yFOD_.js → use-search-index-status-CQ9S05G7.js} +1 -1
- package/src/client/dist/pwa/assets/use-slide-transition-BJh9xGMa.js +1 -0
- package/src/client/dist/pwa/assets/use-tours-BmrZbHjz.js +2 -0
- package/src/client/dist/pwa/assets/use-tours-CU5oA7cz.css +1 -0
- package/src/client/dist/pwa/assets/use-worktree-restore-DcucRlLh.js +1 -0
- package/src/client/dist/pwa/assets/{workspace-templates-D-lPJQ4G.js → workspace-templates-DaG42EEt.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-BbOuqdUe.js → xml-CPO5LI6F.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-gasFQtfk.js → yaml-CoYs82jI.js} +1 -1
- package/src/client/dist/pwa/index.html +10 -10
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/mcp-server/README.md +18 -6
- package/src/client/dist/pwa/assets/ActivityFeed-D58VS9o2.js +0 -2
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-CK3r_fVB.js +0 -1
- package/src/client/dist/pwa/assets/AutoLoopFinalReviewSettings-Q-OlSQ1c.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-DasH1Uk4.js +0 -2
- package/src/client/dist/pwa/assets/CreatePage-DxOes_cF.css +0 -1
- package/src/client/dist/pwa/assets/DiffViewer-CncKlXne.js +0 -8
- package/src/client/dist/pwa/assets/GitPanel-WJlzy2VJ.js +0 -3
- package/src/client/dist/pwa/assets/GitPanel-yHvU38dp.css +0 -1
- package/src/client/dist/pwa/assets/HealthPage-Cnlhrn--.css +0 -1
- package/src/client/dist/pwa/assets/HealthPage-hJY5MurK.js +0 -1
- package/src/client/dist/pwa/assets/MainLayout-DgWElK7w.js +0 -2
- package/src/client/dist/pwa/assets/MemoryProposalList-kgFCm9zz.js +0 -1
- package/src/client/dist/pwa/assets/QBtn-BUSdPaf4.js +0 -1
- package/src/client/dist/pwa/assets/QSelect-DFWyDWCE.js +0 -1
- package/src/client/dist/pwa/assets/SettingsPage-CO3vIpPd.js +0 -9
- package/src/client/dist/pwa/assets/TerminalPanel-BY0lPq6k.js +0 -16
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-B9LE8HtU.css +0 -1
- package/src/client/dist/pwa/assets/WorkspaceAttentionLabels-DiYt3rP3.js +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-BEBwwr81.css +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-K-1ce50d.js +0 -4
- package/src/client/dist/pwa/assets/build-path-tree-DEuOdIIk.js +0 -1
- package/src/client/dist/pwa/assets/custom-sounds-Ggw6V9iF.js +0 -1
- package/src/client/dist/pwa/assets/de-Bk3zQrin.js +0 -17
- package/src/client/dist/pwa/assets/detached-fullscreen-Du3TcYdX.js +0 -1
- package/src/client/dist/pwa/assets/dom-BtYTvLeN.js +0 -2
- package/src/client/dist/pwa/assets/ecl-D05T4iGw.js +0 -1
- package/src/client/dist/pwa/assets/editor.worker-BLJF_oQx.js +0 -33
- package/src/client/dist/pwa/assets/efforts-TGacUPcJ.js +0 -9
- package/src/client/dist/pwa/assets/es-f3xuAWA9.js +0 -17
- package/src/client/dist/pwa/assets/expand-template-DM8gq3Mo.js +0 -1
- package/src/client/dist/pwa/assets/fr-B6c21SG3.js +0 -17
- package/src/client/dist/pwa/assets/i18n-WaE4cZK9.js +0 -1
- package/src/client/dist/pwa/assets/index-C8zAZ-k-.css +0 -1
- package/src/client/dist/pwa/assets/index-CpWBGKdM.js +0 -20
- package/src/client/dist/pwa/assets/it-DUstDpC2.js +0 -17
- package/src/client/dist/pwa/assets/monaco-contributions-DHvm-kp5.js +0 -53
- package/src/client/dist/pwa/assets/network-auth-DTZUKbny.js +0 -1
- package/src/client/dist/pwa/assets/notify-theme-FuTL7HuS.js +0 -1
- package/src/client/dist/pwa/assets/permissionModes-D-Nexnoo.js +0 -36
- package/src/client/dist/pwa/assets/reactivity.esm-bundler-DY1JkAEA.js +0 -1
- package/src/client/dist/pwa/assets/render-chat-markdown-CafzCcmu.js +0 -69
- package/src/client/dist/pwa/assets/runtime-core.esm-bundler-Bqg71Vll.js +0 -1
- package/src/client/dist/pwa/assets/use-is-mobile-CDB_rzM9.js +0 -1
- package/src/client/dist/pwa/assets/use-slide-transition-B_Nu6u-t.js +0 -1
- package/src/client/dist/pwa/assets/use-tours-BWkHp34X.js +0 -2
- package/src/client/dist/pwa/assets/use-tours-CTZuBZOi.css +0 -1
- package/src/client/dist/pwa/assets/use-worktree-restore-DUjZr44N.js +0 -1
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { createStreamingBatcher } from '../../streaming-batcher.js';
|
|
2
|
+
function object(value) {
|
|
3
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
4
|
+
? value
|
|
5
|
+
: undefined;
|
|
6
|
+
}
|
|
7
|
+
function validTokens(inputTokens, outputTokens) {
|
|
8
|
+
return typeof inputTokens === 'number' &&
|
|
9
|
+
Number.isSafeInteger(inputTokens) &&
|
|
10
|
+
inputTokens >= 0 &&
|
|
11
|
+
typeof outputTokens === 'number' &&
|
|
12
|
+
Number.isSafeInteger(outputTokens) &&
|
|
13
|
+
outputTokens >= 0
|
|
14
|
+
? { inputTokens, outputTokens }
|
|
15
|
+
: undefined;
|
|
16
|
+
}
|
|
17
|
+
function validCost(value) {
|
|
18
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0;
|
|
19
|
+
}
|
|
20
|
+
function toolName(tool) {
|
|
21
|
+
const name = tool._meta?.tool_name;
|
|
22
|
+
return typeof name === 'string' && name.trim() ? name : tool.name?.trim() || undefined;
|
|
23
|
+
}
|
|
24
|
+
const BRAINSTORM_COMPLETE = '[BRAINSTORM_COMPLETE]';
|
|
25
|
+
/** One owned session and prompt per launch; the engine filters native session IDs before dispatch. */
|
|
26
|
+
export function createMistralEventMapper(launchId, emit) {
|
|
27
|
+
const scoped = (kind, id) => `vibe:${encodeURIComponent(launchId)}:${kind}:${encodeURIComponent(id)}`;
|
|
28
|
+
const canonicalToolCallId = (nativeId) => scoped('tool', nativeId);
|
|
29
|
+
const batcher = createStreamingBatcher(emit);
|
|
30
|
+
const tools = new Map();
|
|
31
|
+
const compactions = new Map();
|
|
32
|
+
let live = false;
|
|
33
|
+
let finished = false;
|
|
34
|
+
let messageId;
|
|
35
|
+
let messageKind;
|
|
36
|
+
let messageSequence = 0;
|
|
37
|
+
let planSequence = 0;
|
|
38
|
+
let brainstormTail = '';
|
|
39
|
+
let brainstormEmitted = false;
|
|
40
|
+
let baseline;
|
|
41
|
+
let baselineCost;
|
|
42
|
+
let latest;
|
|
43
|
+
let latestCost;
|
|
44
|
+
let sawLiveTokens = false;
|
|
45
|
+
let sawLiveCost = false;
|
|
46
|
+
let tokensReset = false;
|
|
47
|
+
let costReset = false;
|
|
48
|
+
function captureTokens(input, output) {
|
|
49
|
+
const tokens = validTokens(input, output);
|
|
50
|
+
if (!tokens) {
|
|
51
|
+
if (live)
|
|
52
|
+
tokensReset = true;
|
|
53
|
+
else
|
|
54
|
+
latest = undefined;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (live) {
|
|
58
|
+
sawLiveTokens = true;
|
|
59
|
+
if (latest && (tokens.inputTokens < latest.inputTokens || tokens.outputTokens < latest.outputTokens))
|
|
60
|
+
tokensReset = true;
|
|
61
|
+
}
|
|
62
|
+
latest = tokens;
|
|
63
|
+
}
|
|
64
|
+
function captureUsage(update) {
|
|
65
|
+
const meta = update._meta;
|
|
66
|
+
if (meta && ('promptTokens' in meta || 'completionTokens' in meta))
|
|
67
|
+
captureTokens(meta.promptTokens, meta.completionTokens);
|
|
68
|
+
if (update.cost) {
|
|
69
|
+
if (update.cost.currency !== 'USD' || !validCost(update.cost.amount)) {
|
|
70
|
+
if (live)
|
|
71
|
+
costReset = true;
|
|
72
|
+
else
|
|
73
|
+
latestCost = undefined;
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
if (live) {
|
|
77
|
+
sawLiveCost = true;
|
|
78
|
+
if (latestCost !== undefined && update.cost.amount < latestCost)
|
|
79
|
+
costReset = true;
|
|
80
|
+
}
|
|
81
|
+
latestCost = update.cost.amount;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function closeMessage() {
|
|
86
|
+
if (messageId)
|
|
87
|
+
batcher.push({ kind: 'message:end', messageId });
|
|
88
|
+
messageId = undefined;
|
|
89
|
+
messageKind = undefined;
|
|
90
|
+
brainstormTail = '';
|
|
91
|
+
}
|
|
92
|
+
function emitTool(tracked, force = false) {
|
|
93
|
+
if ((!tracked.callReady && !force) || tracked.resultEmitted)
|
|
94
|
+
return;
|
|
95
|
+
const { tool } = tracked;
|
|
96
|
+
const toolCallId = canonicalToolCallId(tool.toolCallId);
|
|
97
|
+
if (!tracked.callEmitted) {
|
|
98
|
+
tracked.callEmitted = true;
|
|
99
|
+
batcher.push({
|
|
100
|
+
kind: 'tool:call',
|
|
101
|
+
messageId: '',
|
|
102
|
+
toolCallId,
|
|
103
|
+
name: toolName(tool) ?? (tool.title || 'Tool'),
|
|
104
|
+
input: tool.rawInput,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (tool.status === 'completed' || tool.status === 'failed') {
|
|
108
|
+
tracked.resultEmitted = true;
|
|
109
|
+
const output = tool.content?.length || tool.locations?.length
|
|
110
|
+
? {
|
|
111
|
+
...(tool.rawOutput !== undefined ? { rawOutput: tool.rawOutput } : {}),
|
|
112
|
+
...(tool.content?.length ? { content: tool.content } : {}),
|
|
113
|
+
...(tool.locations?.length ? { locations: tool.locations } : {}),
|
|
114
|
+
}
|
|
115
|
+
: (tool.rawOutput ?? null);
|
|
116
|
+
batcher.push({ kind: 'tool:result', toolCallId, output, isError: tool.status === 'failed' });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function mergeTool(update) {
|
|
120
|
+
let tracked = tools.get(update.toolCallId);
|
|
121
|
+
if (!tracked) {
|
|
122
|
+
tracked = {
|
|
123
|
+
tool: { toolCallId: update.toolCallId, title: '' },
|
|
124
|
+
hasMetadata: false,
|
|
125
|
+
callReady: false,
|
|
126
|
+
callEmitted: false,
|
|
127
|
+
resultEmitted: false,
|
|
128
|
+
};
|
|
129
|
+
tools.set(update.toolCallId, tracked);
|
|
130
|
+
}
|
|
131
|
+
if (tracked.resultEmitted)
|
|
132
|
+
return;
|
|
133
|
+
const tool = tracked.tool;
|
|
134
|
+
const terminal = tool.status === 'completed' || tool.status === 'failed';
|
|
135
|
+
if (update.title != null)
|
|
136
|
+
tool.title = update.title;
|
|
137
|
+
if (update.name != null)
|
|
138
|
+
tool.name = update.name;
|
|
139
|
+
if (update.kind != null)
|
|
140
|
+
tool.kind = update.kind;
|
|
141
|
+
if (update.status != null && !terminal)
|
|
142
|
+
tool.status = update.status;
|
|
143
|
+
if (update.rawInput !== undefined)
|
|
144
|
+
tool.rawInput = update.rawInput;
|
|
145
|
+
if (update.rawOutput !== undefined && (!terminal || tool.rawOutput === undefined))
|
|
146
|
+
tool.rawOutput = update.rawOutput;
|
|
147
|
+
if (update.content !== undefined)
|
|
148
|
+
tool.content = update.content ?? undefined;
|
|
149
|
+
if (update.locations !== undefined)
|
|
150
|
+
tool.locations = update.locations ?? undefined;
|
|
151
|
+
if (update._meta !== undefined)
|
|
152
|
+
tool._meta = update._meta === null ? undefined : { ...tool._meta, ...update._meta };
|
|
153
|
+
tracked.hasMetadata ||= update.title != null || toolName(tool) !== undefined;
|
|
154
|
+
tracked.callReady ||=
|
|
155
|
+
update.sessionUpdate === 'tool_call' || (toolName(tool) !== undefined && tool.rawInput !== undefined);
|
|
156
|
+
emitTool(tracked);
|
|
157
|
+
}
|
|
158
|
+
function flush() {
|
|
159
|
+
if (!live)
|
|
160
|
+
return;
|
|
161
|
+
closeMessage();
|
|
162
|
+
for (const tracked of tools.values())
|
|
163
|
+
emitTool(tracked, true);
|
|
164
|
+
batcher.close();
|
|
165
|
+
}
|
|
166
|
+
return {
|
|
167
|
+
canonicalToolCallId,
|
|
168
|
+
getTool(nativeId) {
|
|
169
|
+
const tracked = tools.get(nativeId);
|
|
170
|
+
return tracked?.hasMetadata ? tracked.tool : undefined;
|
|
171
|
+
},
|
|
172
|
+
beginLive(explicit) {
|
|
173
|
+
if (live || finished)
|
|
174
|
+
return;
|
|
175
|
+
baseline = explicit ? validTokens(explicit.inputTokens, explicit.outputTokens) : latest;
|
|
176
|
+
baselineCost = explicit ? (validCost(explicit.costUsd) ? explicit.costUsd : undefined) : latestCost;
|
|
177
|
+
latest = baseline;
|
|
178
|
+
latestCost = baselineCost;
|
|
179
|
+
live = true;
|
|
180
|
+
},
|
|
181
|
+
onUpdate({ update }) {
|
|
182
|
+
if (finished)
|
|
183
|
+
return;
|
|
184
|
+
if (update.sessionUpdate === 'usage_update') {
|
|
185
|
+
captureUsage(update);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (!live)
|
|
189
|
+
return;
|
|
190
|
+
switch (update.sessionUpdate) {
|
|
191
|
+
case 'agent_message_chunk':
|
|
192
|
+
case 'agent_thought_chunk': {
|
|
193
|
+
if (update.content.type !== 'text')
|
|
194
|
+
return;
|
|
195
|
+
if (messageKind && messageKind !== update.sessionUpdate)
|
|
196
|
+
closeMessage();
|
|
197
|
+
const nextId = update.messageId
|
|
198
|
+
? scoped('message', update.messageId)
|
|
199
|
+
: (messageId ?? scoped('anonymous-message', String(++messageSequence)));
|
|
200
|
+
if (messageId && messageId !== nextId)
|
|
201
|
+
closeMessage();
|
|
202
|
+
messageId = nextId;
|
|
203
|
+
messageKind = update.sessionUpdate;
|
|
204
|
+
if (update.sessionUpdate === 'agent_thought_chunk') {
|
|
205
|
+
batcher.close();
|
|
206
|
+
batcher.push({ kind: 'message:thinking', messageId, text: update.content.text, streaming: true });
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
batcher.push({ kind: 'message:text', messageId, text: update.content.text, streaming: true });
|
|
210
|
+
if (!brainstormEmitted) {
|
|
211
|
+
const text = brainstormTail + update.content.text;
|
|
212
|
+
if (text.includes(BRAINSTORM_COMPLETE)) {
|
|
213
|
+
brainstormEmitted = true;
|
|
214
|
+
batcher.push({ kind: 'session:brainstorm-complete' });
|
|
215
|
+
}
|
|
216
|
+
brainstormTail = text.slice(-(BRAINSTORM_COMPLETE.length - 1));
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
case 'tool_call':
|
|
222
|
+
case 'tool_call_update':
|
|
223
|
+
closeMessage();
|
|
224
|
+
mergeTool(update);
|
|
225
|
+
return;
|
|
226
|
+
case 'plan': {
|
|
227
|
+
closeMessage();
|
|
228
|
+
const toolCallId = scoped('plan', String(++planSequence));
|
|
229
|
+
batcher.push({
|
|
230
|
+
kind: 'tool:call',
|
|
231
|
+
messageId: '',
|
|
232
|
+
toolCallId,
|
|
233
|
+
name: 'TodoWrite',
|
|
234
|
+
input: { todos: update.entries.map(({ content, status }) => ({ content, status })) },
|
|
235
|
+
});
|
|
236
|
+
batcher.push({ kind: 'tool:result', toolCallId, output: null, isError: false });
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
case 'notice':
|
|
240
|
+
if (update.severity === 'error')
|
|
241
|
+
batcher.push({ kind: 'error', category: 'other', message: update.description || update.title });
|
|
242
|
+
return;
|
|
243
|
+
case 'compaction_update': {
|
|
244
|
+
const previous = compactions.get(update.compactionId);
|
|
245
|
+
if (previous === update.status || (previous && previous !== 'in_progress'))
|
|
246
|
+
return;
|
|
247
|
+
const compactionId = scoped('compaction', update.compactionId);
|
|
248
|
+
if (update.status === 'in_progress') {
|
|
249
|
+
compactions.set(update.compactionId, update.status);
|
|
250
|
+
batcher.push({ kind: 'session:compacting', active: true, compactionId });
|
|
251
|
+
}
|
|
252
|
+
else if (['completed', 'failed', 'cancelled'].includes(update.status)) {
|
|
253
|
+
compactions.set(update.compactionId, update.status);
|
|
254
|
+
batcher.push({ kind: 'session:compacting', active: false, compactionId });
|
|
255
|
+
if (update.status === 'completed')
|
|
256
|
+
batcher.push({ kind: 'session:compacted', compactionId });
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
finish(result) {
|
|
263
|
+
if (!live || finished)
|
|
264
|
+
return;
|
|
265
|
+
// Stable SDK responses are unchecked; native cumulative usage is not in PromptResponse's type.
|
|
266
|
+
const response = object(result);
|
|
267
|
+
const nativeUsage = object(response?.usage);
|
|
268
|
+
if (nativeUsage && ('inputTokens' in nativeUsage || 'outputTokens' in nativeUsage))
|
|
269
|
+
captureTokens(nativeUsage.inputTokens, nativeUsage.outputTokens);
|
|
270
|
+
flush();
|
|
271
|
+
finished = true;
|
|
272
|
+
if (baseline && latest && sawLiveTokens && !tokensReset) {
|
|
273
|
+
batcher.push({
|
|
274
|
+
kind: 'usage',
|
|
275
|
+
inputTokens: latest.inputTokens - baseline.inputTokens,
|
|
276
|
+
outputTokens: latest.outputTokens - baseline.outputTokens,
|
|
277
|
+
...(baselineCost !== undefined && latestCost !== undefined && sawLiveCost && !costReset
|
|
278
|
+
? { costUsd: latestCost - baselineCost }
|
|
279
|
+
: {}),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
const stopReason = response?.stopReason;
|
|
283
|
+
if (stopReason === 'max_tokens' || stopReason === 'max_turn_requests' || stopReason === 'refusal') {
|
|
284
|
+
batcher.push({
|
|
285
|
+
kind: 'error',
|
|
286
|
+
category: 'other',
|
|
287
|
+
message: `Vibe prompt stopped: ${stopReason}`,
|
|
288
|
+
code: `acp_${stopReason}`,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
else if (stopReason !== 'end_turn' && stopReason !== 'cancelled') {
|
|
292
|
+
batcher.push({ kind: 'error', category: 'parse_error', message: 'Invalid Vibe ACP prompt stop reason' });
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
flush,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { buildKoboLaunchInstructions, getKoboPromptCapabilities, } from '../../../../../shared/launch-instructions.js';
|
|
3
|
+
import { assertMistralSelection, MISTRAL_MODES } from './configuration.js';
|
|
4
|
+
export function buildMistralOptions(options) {
|
|
5
|
+
if (options.resumeFromEngineSessionId !== undefined && !options.resumeFromEngineSessionId.trim())
|
|
6
|
+
throw new Error('Mistral Vibe requires a nonempty saved session identity');
|
|
7
|
+
const permissionMode = options.agentPermissionMode ?? 'interactive';
|
|
8
|
+
const capabilities = getKoboPromptCapabilities(options.mcpServers?.find((server) => server.name === 'kobo-tasks')?.env);
|
|
9
|
+
if (permissionMode === 'plan' || options.readOnly || capabilities.handoff)
|
|
10
|
+
throw new Error('Mistral Vibe does not support Plan or read-only launches');
|
|
11
|
+
assertMistralSelection(options.model, 'model');
|
|
12
|
+
assertMistralSelection(options.effort, 'thinking');
|
|
13
|
+
if (!Object.hasOwn(MISTRAL_MODES, permissionMode))
|
|
14
|
+
throw new Error('Unsupported Mistral Vibe permission mode');
|
|
15
|
+
const modeId = MISTRAL_MODES[permissionMode];
|
|
16
|
+
const koboInstructions = buildKoboLaunchInstructions({
|
|
17
|
+
engine: 'mistral-vibe',
|
|
18
|
+
permissionMode,
|
|
19
|
+
autoLoop: options.autoLoop,
|
|
20
|
+
...capabilities,
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
cwd: resolve(options.workingDir),
|
|
24
|
+
modeId,
|
|
25
|
+
koboInstructions,
|
|
26
|
+
mcpServers: (options.mcpServers ?? []).map((server) => ({
|
|
27
|
+
name: server.name,
|
|
28
|
+
command: server.command,
|
|
29
|
+
args: [...server.args],
|
|
30
|
+
env: Object.entries(server.env).map(([name, value]) => ({ name, value })),
|
|
31
|
+
})),
|
|
32
|
+
prompt: [{ type: 'text', text: `${koboInstructions.promptPrefix}\n\n${options.prompt}` }],
|
|
33
|
+
env: { ...process.env, ...options.env },
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { isOperationApprovalAvailable } from '../../../workspace-permission-policy-service.js';
|
|
2
|
+
import { PendingUserInputValidationError } from '../types.js';
|
|
3
|
+
function record(value) {
|
|
4
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
function onlyKeys(value, keys) {
|
|
7
|
+
return Object.keys(value).every((key) => keys.includes(key));
|
|
8
|
+
}
|
|
9
|
+
function text(value, max = 4000) {
|
|
10
|
+
return typeof value === 'string' && value.length <= max;
|
|
11
|
+
}
|
|
12
|
+
function bound(value, fallback, ceiling) {
|
|
13
|
+
if (value == null)
|
|
14
|
+
return fallback;
|
|
15
|
+
return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 && value <= ceiling ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
function choices(schema, titledKey) {
|
|
18
|
+
if (schema.enum != null && schema[titledKey] != null)
|
|
19
|
+
return undefined;
|
|
20
|
+
let options;
|
|
21
|
+
if (Array.isArray(schema.enum) && schema.enum.every((value) => text(value, 2000))) {
|
|
22
|
+
options = schema.enum.map((label) => ({ label }));
|
|
23
|
+
}
|
|
24
|
+
else if (Array.isArray(schema[titledKey])) {
|
|
25
|
+
const values = schema[titledKey];
|
|
26
|
+
if (!values.every((v) => record(v) &&
|
|
27
|
+
onlyKeys(v, ['const', 'title', 'description', '_meta']) &&
|
|
28
|
+
text(v.const, 2000) &&
|
|
29
|
+
text(v.title, 2000) &&
|
|
30
|
+
(v.description == null || text(v.description))))
|
|
31
|
+
return undefined;
|
|
32
|
+
options = values.map((v) => ({ label: v.const, description: (v.description ?? v.title) }));
|
|
33
|
+
}
|
|
34
|
+
else
|
|
35
|
+
return undefined;
|
|
36
|
+
if (!options.length || options.length > 32 || new Set(options.map((o) => o.label)).size !== options.length)
|
|
37
|
+
return undefined;
|
|
38
|
+
return options;
|
|
39
|
+
}
|
|
40
|
+
function formFields(schema) {
|
|
41
|
+
if (!record(schema) ||
|
|
42
|
+
!onlyKeys(schema, ['type', 'title', 'description', 'properties', 'required', '_meta']) ||
|
|
43
|
+
(schema.type != null && schema.type !== 'object') ||
|
|
44
|
+
!record(schema.properties))
|
|
45
|
+
return undefined;
|
|
46
|
+
const entries = Object.entries(schema.properties);
|
|
47
|
+
if (!entries.length || entries.length > 20)
|
|
48
|
+
return undefined;
|
|
49
|
+
const required = schema.required ?? [];
|
|
50
|
+
if (!Array.isArray(required) ||
|
|
51
|
+
required.some((key) => typeof key !== 'string' || !Object.hasOwn(schema.properties, key)) ||
|
|
52
|
+
new Set(required).size !== required.length)
|
|
53
|
+
return undefined;
|
|
54
|
+
const fields = [];
|
|
55
|
+
for (const [id, property] of entries) {
|
|
56
|
+
if (!id ||
|
|
57
|
+
!text(id, 200) ||
|
|
58
|
+
!record(property) ||
|
|
59
|
+
(property.title != null && !text(property.title)) ||
|
|
60
|
+
(property.description != null && !text(property.description)))
|
|
61
|
+
return undefined;
|
|
62
|
+
const multiSelect = property.type === 'array';
|
|
63
|
+
if (property.type !== 'string' && !multiSelect)
|
|
64
|
+
return undefined;
|
|
65
|
+
if (!onlyKeys(property, multiSelect
|
|
66
|
+
? ['type', 'title', 'description', 'items', 'minItems', 'maxItems', 'default', '_meta']
|
|
67
|
+
: [
|
|
68
|
+
'type',
|
|
69
|
+
'title',
|
|
70
|
+
'description',
|
|
71
|
+
'minLength',
|
|
72
|
+
'maxLength',
|
|
73
|
+
'enum',
|
|
74
|
+
'oneOf',
|
|
75
|
+
'default',
|
|
76
|
+
'pattern',
|
|
77
|
+
'format',
|
|
78
|
+
'_meta',
|
|
79
|
+
]))
|
|
80
|
+
return undefined;
|
|
81
|
+
// Pattern/format constraints need native validation support before they can be rendered safely.
|
|
82
|
+
if (property.pattern != null || property.format != null)
|
|
83
|
+
return undefined;
|
|
84
|
+
let options = [];
|
|
85
|
+
if (multiSelect) {
|
|
86
|
+
if (!record(property.items) ||
|
|
87
|
+
!onlyKeys(property.items, ['type', 'enum', 'anyOf', '_meta']) ||
|
|
88
|
+
(property.items.type != null && property.items.type !== 'string'))
|
|
89
|
+
return undefined;
|
|
90
|
+
const parsed = choices(property.items, 'anyOf');
|
|
91
|
+
if (!parsed)
|
|
92
|
+
return undefined;
|
|
93
|
+
options = parsed;
|
|
94
|
+
}
|
|
95
|
+
else if (property.enum != null || property.oneOf != null) {
|
|
96
|
+
const parsed = choices(property, 'oneOf');
|
|
97
|
+
if (!parsed)
|
|
98
|
+
return undefined;
|
|
99
|
+
options = parsed;
|
|
100
|
+
}
|
|
101
|
+
const min = bound(multiSelect ? property.minItems : property.minLength, 0, multiSelect ? options.length : 10000);
|
|
102
|
+
const max = bound(multiSelect ? property.maxItems : property.maxLength, multiSelect ? options.length : 10000, multiSelect ? options.length : 10000);
|
|
103
|
+
if (min === undefined || max === undefined || min > max)
|
|
104
|
+
return undefined;
|
|
105
|
+
fields.push({
|
|
106
|
+
id,
|
|
107
|
+
question: (property.description ?? property.title ?? id),
|
|
108
|
+
...(property.title ? { header: property.title } : {}),
|
|
109
|
+
options,
|
|
110
|
+
required: required.includes(id),
|
|
111
|
+
freeText: !multiSelect && !options.length,
|
|
112
|
+
multiSelect,
|
|
113
|
+
isOther: false,
|
|
114
|
+
min,
|
|
115
|
+
max,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return fields;
|
|
119
|
+
}
|
|
120
|
+
function formAnswer(fields, response) {
|
|
121
|
+
if (!record(response.answers) || (response.selections !== undefined && !record(response.selections)))
|
|
122
|
+
return undefined;
|
|
123
|
+
if (Object.keys(response.answers).some((id) => !fields.some((field) => field.id === id)) ||
|
|
124
|
+
Object.keys(response.selections ?? {}).some((id) => !fields.some((field) => field.id === id && field.multiSelect)))
|
|
125
|
+
return undefined;
|
|
126
|
+
const content = [];
|
|
127
|
+
for (const field of fields) {
|
|
128
|
+
const source = field.multiSelect ? response.selections : response.answers;
|
|
129
|
+
if (!source || !Object.hasOwn(source, field.id)) {
|
|
130
|
+
if (field.required || Object.hasOwn(response.answers, field.id))
|
|
131
|
+
return undefined;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const value = source[field.id];
|
|
135
|
+
if (field.multiSelect) {
|
|
136
|
+
if (!Array.isArray(value) ||
|
|
137
|
+
value.length < field.min ||
|
|
138
|
+
value.length > field.max ||
|
|
139
|
+
new Set(value).size !== value.length ||
|
|
140
|
+
value.some((v) => !text(v, 2000) || !field.options.some((o) => o.label === v)))
|
|
141
|
+
return undefined;
|
|
142
|
+
content.push([field.id, [...value]]);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
if (!text(value, field.max) ||
|
|
146
|
+
value.length < field.min ||
|
|
147
|
+
(!field.freeText && !field.options.some((o) => o.label === value)))
|
|
148
|
+
return undefined;
|
|
149
|
+
content.push([field.id, value]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return { action: 'accept', content: Object.fromEntries(content) };
|
|
153
|
+
}
|
|
154
|
+
export function createMistralServerRequests(args) {
|
|
155
|
+
const pending = new Map();
|
|
156
|
+
const seen = new Set();
|
|
157
|
+
let closed = false;
|
|
158
|
+
function wait(key, signal, cancelled, answer, event) {
|
|
159
|
+
return new Promise((resolve) => {
|
|
160
|
+
function settle(result) {
|
|
161
|
+
if (!pending.delete(key))
|
|
162
|
+
return;
|
|
163
|
+
signal.removeEventListener('abort', cancel);
|
|
164
|
+
resolve(result);
|
|
165
|
+
args.onPendingChange?.(pending.size);
|
|
166
|
+
}
|
|
167
|
+
const cancel = () => settle(cancelled);
|
|
168
|
+
pending.set(key, {
|
|
169
|
+
cancel,
|
|
170
|
+
answer: (response) => {
|
|
171
|
+
const result = answer(response);
|
|
172
|
+
if (result === undefined)
|
|
173
|
+
throw new PendingUserInputValidationError();
|
|
174
|
+
settle(result);
|
|
175
|
+
return true;
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
seen.add(key);
|
|
179
|
+
signal.addEventListener('abort', cancel, { once: true });
|
|
180
|
+
args.onPendingChange?.(pending.size);
|
|
181
|
+
if (pending.has(key))
|
|
182
|
+
args.emit(event);
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
function unavailable(key, signal) {
|
|
186
|
+
return closed || signal.aborted || seen.has(key) || pending.size >= 32 || seen.size >= 4096;
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
requestPermission({ params, signal }) {
|
|
190
|
+
const cancelled = { outcome: { outcome: 'cancelled' } };
|
|
191
|
+
if (closed || signal.aborted || args.readOnly || params.sessionId !== args.sessionId)
|
|
192
|
+
return cancelled;
|
|
193
|
+
const tool = args.getTool(params.toolCall.toolCallId);
|
|
194
|
+
const toolName = tool?._meta?.tool_name;
|
|
195
|
+
if (!tool || typeof toolName !== 'string' || !toolName.trim() || toolName.length > 200)
|
|
196
|
+
return cancelled;
|
|
197
|
+
const key = args.canonicalToolCallId(params.toolCall.toolCallId);
|
|
198
|
+
if (unavailable(key, signal))
|
|
199
|
+
return cancelled;
|
|
200
|
+
const offered = params.options;
|
|
201
|
+
if (!offered.length ||
|
|
202
|
+
offered.some((option) => !option.optionId) ||
|
|
203
|
+
new Set(offered.map((option) => option.optionId)).size !== offered.length)
|
|
204
|
+
return cancelled;
|
|
205
|
+
const allow = offered.find((option) => option.kind === 'allow_once');
|
|
206
|
+
const deny = offered.find((option) => option.kind === 'reject_once');
|
|
207
|
+
if (!allow || !deny)
|
|
208
|
+
return cancelled;
|
|
209
|
+
const payload = {
|
|
210
|
+
rawInput: tool.rawInput ?? null,
|
|
211
|
+
effectKind: tool._meta?.effect_kind ?? null,
|
|
212
|
+
kind: tool.kind ?? null,
|
|
213
|
+
operationApprovalAvailable: false,
|
|
214
|
+
};
|
|
215
|
+
payload.operationApprovalAvailable = isOperationApprovalAvailable({
|
|
216
|
+
engine: 'mistral-vibe',
|
|
217
|
+
toolName,
|
|
218
|
+
payload: { ...payload, operationApprovalAvailable: true },
|
|
219
|
+
});
|
|
220
|
+
const selected = (optionId) => ({ outcome: { outcome: 'selected', optionId } });
|
|
221
|
+
const approved = args.autoApprove?.(toolName, payload);
|
|
222
|
+
if (closed || signal.aborted)
|
|
223
|
+
return cancelled;
|
|
224
|
+
if (approved) {
|
|
225
|
+
seen.add(key);
|
|
226
|
+
return selected(allow.optionId);
|
|
227
|
+
}
|
|
228
|
+
return wait(key, signal, cancelled, (response) => response.kind === 'permission-allow'
|
|
229
|
+
? selected(allow.optionId)
|
|
230
|
+
: response.kind === 'permission-deny'
|
|
231
|
+
? selected(deny.optionId)
|
|
232
|
+
: undefined, { kind: 'session:user-input-requested', requestKind: 'permission', toolCallId: key, toolName, payload });
|
|
233
|
+
},
|
|
234
|
+
createElicitation({ params, signal, requestId }) {
|
|
235
|
+
if (closed || signal.aborted || args.readOnly)
|
|
236
|
+
return { action: 'cancel' };
|
|
237
|
+
if (params.mode !== 'form' ||
|
|
238
|
+
!('sessionId' in params) ||
|
|
239
|
+
params.sessionId !== args.sessionId ||
|
|
240
|
+
!text(params.message))
|
|
241
|
+
return { action: 'decline' };
|
|
242
|
+
const fields = formFields(params.requestedSchema);
|
|
243
|
+
if (!fields)
|
|
244
|
+
return { action: 'decline' };
|
|
245
|
+
const nativeId = typeof params.toolCallId === 'string' ? params.toolCallId : '';
|
|
246
|
+
const key = `${args.canonicalToolCallId(nativeId)}:elicitation:${typeof requestId}:${encodeURIComponent(String(requestId))}`;
|
|
247
|
+
if (unavailable(key, signal))
|
|
248
|
+
return { action: 'decline' };
|
|
249
|
+
return wait(key, signal, { action: 'cancel' }, (response) => response.kind === 'question-cancel'
|
|
250
|
+
? { action: 'cancel' }
|
|
251
|
+
: response.kind === 'question'
|
|
252
|
+
? formAnswer(fields, response)
|
|
253
|
+
: undefined, {
|
|
254
|
+
kind: 'session:user-input-requested',
|
|
255
|
+
requestKind: 'question',
|
|
256
|
+
toolCallId: key,
|
|
257
|
+
toolName: 'AskUserQuestion',
|
|
258
|
+
payload: {
|
|
259
|
+
questions: fields.map(({ min, max, ...field }) => ({
|
|
260
|
+
...field,
|
|
261
|
+
...(field.multiSelect ? { minItems: min, maxItems: max } : { minLength: min, maxLength: max }),
|
|
262
|
+
})),
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
hasPendingUserInput(toolCallId) {
|
|
267
|
+
return pending.has(toolCallId);
|
|
268
|
+
},
|
|
269
|
+
resolvePendingUserInput(toolCallId, response) {
|
|
270
|
+
return pending.get(toolCallId)?.answer(response) ?? false;
|
|
271
|
+
},
|
|
272
|
+
cancelAll() {
|
|
273
|
+
closed = true;
|
|
274
|
+
for (const entry of pending.values())
|
|
275
|
+
entry.cancel();
|
|
276
|
+
},
|
|
277
|
+
};
|
|
278
|
+
}
|