@bivy/bivy 0.10.1-staging.355 → 0.10.1-staging.357
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.
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2026 Petter André Sjulstad
|
|
3
|
+
//
|
|
4
|
+
// The credential CRUD + presets command cluster, extracted from server.ts's
|
|
5
|
+
// RELAY_COMMANDS (server.ts decomposition; same shape as controllers/
|
|
6
|
+
// session-control.ts and fork-commands.ts). The credential vault operations
|
|
7
|
+
// already live in credentials/api.ts + runtime/credentials.ts and take the node's
|
|
8
|
+
// credsDir explicitly, so this controller imports them directly and only injects
|
|
9
|
+
// the handful of daemon bits (credsDir, event emit, and the post-auth refresh /
|
|
10
|
+
// provider-list helpers) as composition deps.
|
|
11
|
+
import { testProviderCredential } from "../runtime/credentials.js";
|
|
12
|
+
import { exportAccountApiKeys, exportRecordTombstones, listCredentialRecords, removeProviderCredential, setProviderApiKeyLabeled, setProviderReferenceLabeled, setCredentialSync, setCredentialUnattended, getCredentialPresets, setActiveCredentialPreset, setCredentialPresetMapping, } from "../credentials/api.js";
|
|
13
|
+
export function createCredentialCommands(deps) {
|
|
14
|
+
const { credsDir } = deps;
|
|
15
|
+
return {
|
|
16
|
+
async "credentials.list"() {
|
|
17
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
18
|
+
},
|
|
19
|
+
async "credentials.account.export"(_msg, ctx) {
|
|
20
|
+
// This reply travels inside the already-paired E2E node channel. It contains
|
|
21
|
+
// API keys only; OAuth/ref/node-local material is excluded by the API.
|
|
22
|
+
ctx.reply({ type: "credentials.account.export", requestId: _msg.requestId, entries: await exportAccountApiKeys(credsDir), records: await listCredentialRecords(credsDir), deletedAt: await exportRecordTombstones(credsDir) });
|
|
23
|
+
},
|
|
24
|
+
async "credential.set"(msg, ctx) {
|
|
25
|
+
try {
|
|
26
|
+
const provider = String(msg.provider ?? "").trim().toLowerCase();
|
|
27
|
+
const label = String(msg.label ?? "");
|
|
28
|
+
const ref = typeof msg.ref === "string" ? msg.ref.trim() : "";
|
|
29
|
+
const requestedSync = msg.sync === "account" || msg.sync === "node" ? msg.sync : undefined;
|
|
30
|
+
// Persist the secret and its requested tier atomically. Creating a
|
|
31
|
+
// machine-only credential must never leave an account-tier crash window.
|
|
32
|
+
if (ref)
|
|
33
|
+
await setProviderReferenceLabeled(credsDir, provider, label, ref, requestedSync);
|
|
34
|
+
else
|
|
35
|
+
await setProviderApiKeyLabeled(credsDir, provider, label, String(msg.key ?? ""), requestedSync);
|
|
36
|
+
await deps.pushModelAuthToControlPlane();
|
|
37
|
+
await deps.refreshSessionAfterAuth();
|
|
38
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
39
|
+
deps.broadcast({ type: "providers.list", providers: await deps.listProvidersUnified() });
|
|
40
|
+
ctx.reply({ type: "credential.set.ok", requestId: msg.requestId });
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
deps.sendEvent({ type: "session.error", error: message });
|
|
45
|
+
ctx.reply({ type: "credential.set.error", requestId: msg.requestId, error: message });
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
async "credential.remove"(msg, ctx) {
|
|
49
|
+
try {
|
|
50
|
+
await removeProviderCredential(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""));
|
|
51
|
+
await deps.pushModelAuthToControlPlane();
|
|
52
|
+
await deps.refreshSessionAfterAuth();
|
|
53
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
54
|
+
deps.broadcast({ type: "providers.list", providers: await deps.listProvidersUnified() });
|
|
55
|
+
ctx.reply({ type: "credential.remove.ok", requestId: msg.requestId });
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
59
|
+
deps.sendEvent({ type: "session.error", error: message });
|
|
60
|
+
ctx.reply({ type: "credential.remove.error", requestId: msg.requestId, error: message });
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async "credential.sync.set"(msg, ctx) {
|
|
64
|
+
try {
|
|
65
|
+
const sync = msg.sync === "node" ? "node" : "account";
|
|
66
|
+
await setCredentialSync(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""), sync);
|
|
67
|
+
await deps.pushModelAuthToControlPlane();
|
|
68
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
69
|
+
ctx.reply({ type: "credential.sync.set.ok", requestId: msg.requestId });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
73
|
+
deps.sendEvent({ type: "session.error", error: message });
|
|
74
|
+
ctx.reply({ type: "credential.sync.set.error", requestId: msg.requestId, error: message });
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
async "credential.unattended.set"(msg, ctx) {
|
|
78
|
+
try {
|
|
79
|
+
await setCredentialUnattended(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""), msg.unattended === true);
|
|
80
|
+
await deps.pushModelAuthToControlPlane();
|
|
81
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
82
|
+
ctx.reply({ type: "credential.unattended.set.ok", requestId: msg.requestId });
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
86
|
+
ctx.reply({ type: "credential.unattended.set.error", requestId: msg.requestId, error: message });
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
// "Test connection": a bounded, non-secret liveness probe for one credential
|
|
90
|
+
// record (see credentials/api.ts testCredential). The reply carries only
|
|
91
|
+
// ok/at/reason — the credential's own token never leaves this handler.
|
|
92
|
+
async "credential.test"(msg, ctx) {
|
|
93
|
+
const provider = String(msg.provider ?? "").trim().toLowerCase();
|
|
94
|
+
const label = String(msg.label ?? "");
|
|
95
|
+
try {
|
|
96
|
+
const result = await testProviderCredential(credsDir, provider, label);
|
|
97
|
+
ctx.reply({ type: "credential.test.result", requestId: msg.requestId, provider, label, ...result });
|
|
98
|
+
deps.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
ctx.reply({ type: "credential.test.result", requestId: msg.requestId, provider, label, ok: false, at: Date.now(), reason: "network_error" });
|
|
102
|
+
deps.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
async "credentials.presets.get"() {
|
|
106
|
+
deps.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
107
|
+
},
|
|
108
|
+
async "credentials.presets.setActive"(msg) {
|
|
109
|
+
try {
|
|
110
|
+
setActiveCredentialPreset(credsDir, String(msg.active ?? ""));
|
|
111
|
+
await deps.refreshSessionAfterAuth();
|
|
112
|
+
deps.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
deps.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
async "credentials.presets.setMapping"(msg, ctx) {
|
|
119
|
+
try {
|
|
120
|
+
setCredentialPresetMapping(credsDir, String(msg.preset ?? ""), String(msg.provider ?? ""), String(msg.label ?? ""));
|
|
121
|
+
await deps.refreshSessionAfterAuth();
|
|
122
|
+
deps.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
123
|
+
ctx.reply({ type: "credentials.presets.setMapping.ok", requestId: msg.requestId });
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
127
|
+
ctx.reply({ type: "credentials.presets.setMapping.error", requestId: msg.requestId, error: message });
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2026 Petter André Sjulstad
|
|
3
|
+
//
|
|
4
|
+
// The local/custom-model registry command cluster (models.custom.*), extracted
|
|
5
|
+
// from server.ts's RELAY_COMMANDS (server.ts decomposition; same shape as the
|
|
6
|
+
// other controllers/*). The registry itself lives in the model controller; these
|
|
7
|
+
// handlers are thin event-wiring over its operations, injected as deps.
|
|
8
|
+
export function createCustomModelCommands(deps) {
|
|
9
|
+
return {
|
|
10
|
+
async "models.custom.list"() {
|
|
11
|
+
deps.sendEvent({ type: "models.custom.list", providers: await deps.localModelSummaries() });
|
|
12
|
+
},
|
|
13
|
+
async "models.custom.presets"() {
|
|
14
|
+
deps.sendEvent({ type: "models.custom.presets", presets: await deps.localModelPresets() });
|
|
15
|
+
},
|
|
16
|
+
async "models.custom.discover"(_msg, ctx) {
|
|
17
|
+
try {
|
|
18
|
+
ctx.reply({ type: "models.custom.discover.ok", ...(await deps.discoverModelsOnMachine()) });
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
ctx.reply({ type: "models.custom.discover.error", error: error instanceof Error ? error.message : String(error) });
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
async "models.custom.verify"(msg, ctx) {
|
|
25
|
+
try {
|
|
26
|
+
ctx.reply({ type: "models.custom.verify.ok", result: await deps.verifyModelEndpoint(msg) });
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
ctx.reply({ type: "models.custom.verify.error", error: error instanceof Error ? error.message : String(error) });
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
async "models.custom.save"(msg, ctx) {
|
|
33
|
+
try {
|
|
34
|
+
// The client sends the same field set as the REST body (baseUrl, api,
|
|
35
|
+
// apiKey, models[], compat, name, providerId). persistLocalModelSave
|
|
36
|
+
// broadcasts the refreshed list to every client, requester included.
|
|
37
|
+
const result = await deps.persistLocalModelSave(msg?.spec ?? msg);
|
|
38
|
+
// Return the normalized (Machine-scoped) provider id so the PWA can make
|
|
39
|
+
// the imported model the next draft's explicit choice.
|
|
40
|
+
ctx.reply({ type: "models.custom.save.ok", requestId: msg.requestId, provider: result.id });
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
44
|
+
deps.sendEvent({ type: "session.error", error: message });
|
|
45
|
+
ctx.reply({ type: "models.custom.save.error", requestId: msg.requestId, error: message });
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
async "models.custom.remove"(msg) {
|
|
49
|
+
try {
|
|
50
|
+
await deps.persistLocalModelRemove(String(msg.id ?? msg.provider ?? ""));
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
deps.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -98,6 +98,8 @@ import { createAccessDeviceController, createLinkedDeviceController } from "./co
|
|
|
98
98
|
import { createSessionControlCommands } from "./controllers/session-control.js";
|
|
99
99
|
import { createForkCommands } from "./controllers/fork-commands.js";
|
|
100
100
|
import { createGithubCommands } from "./controllers/github-commands.js";
|
|
101
|
+
import { createCredentialCommands } from "./controllers/credential-commands.js";
|
|
102
|
+
import { createCustomModelCommands } from "./controllers/custom-model-commands.js";
|
|
101
103
|
import { MetadataStore } from "./metadata.js";
|
|
102
104
|
import { resolveResumeRef, resumeRefFor } from "./session-ref.js";
|
|
103
105
|
import { materializeFork } from "./session/fork.js";
|
|
@@ -1952,6 +1954,25 @@ const RELAY_COMMANDS = {
|
|
|
1952
1954
|
connectExistingApp,
|
|
1953
1955
|
disconnectGithubApp,
|
|
1954
1956
|
}),
|
|
1957
|
+
// Credential CRUD + presets cluster, extracted to controllers/credential-commands.ts.
|
|
1958
|
+
...createCredentialCommands({
|
|
1959
|
+
credsDir,
|
|
1960
|
+
sendEvent: (event) => relay?.sendEvent(event),
|
|
1961
|
+
broadcast,
|
|
1962
|
+
pushModelAuthToControlPlane,
|
|
1963
|
+
refreshSessionAfterAuth,
|
|
1964
|
+
listProvidersUnified,
|
|
1965
|
+
}),
|
|
1966
|
+
// Local/custom-model registry cluster, extracted to controllers/custom-model-commands.ts.
|
|
1967
|
+
...createCustomModelCommands({
|
|
1968
|
+
sendEvent: (event) => relay?.sendEvent(event),
|
|
1969
|
+
localModelSummaries,
|
|
1970
|
+
localModelPresets,
|
|
1971
|
+
discoverModelsOnMachine,
|
|
1972
|
+
verifyModelEndpoint,
|
|
1973
|
+
persistLocalModelSave,
|
|
1974
|
+
persistLocalModelRemove,
|
|
1975
|
+
}),
|
|
1955
1976
|
// Live-stream gap recovery: replay the session.events a client missed after the
|
|
1956
1977
|
// last seq it holds, or tell it to full-resync (mode:"reset") when the ring has
|
|
1957
1978
|
// evicted past that point. Answers only the caller (ctx.reply); other clients
|
|
@@ -2581,168 +2602,6 @@ const RELAY_COMMANDS = {
|
|
|
2581
2602
|
relay?.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
2582
2603
|
}
|
|
2583
2604
|
},
|
|
2584
|
-
// --- Multiple credentials per provider (labeled). ----------------------
|
|
2585
|
-
async "credentials.list"() {
|
|
2586
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2587
|
-
},
|
|
2588
|
-
async "credentials.account.export"(_msg, ctx) {
|
|
2589
|
-
// This reply travels inside the already-paired E2E node channel. It contains
|
|
2590
|
-
// API keys only; OAuth/ref/node-local material is excluded by the API.
|
|
2591
|
-
ctx.reply({ type: "credentials.account.export", requestId: _msg.requestId, entries: await exportAccountApiKeys(credsDir), records: await listCredentialRecords(credsDir), deletedAt: await exportRecordTombstones(credsDir) });
|
|
2592
|
-
},
|
|
2593
|
-
async "credential.set"(msg, ctx) {
|
|
2594
|
-
try {
|
|
2595
|
-
const provider = String(msg.provider ?? "").trim().toLowerCase();
|
|
2596
|
-
const label = String(msg.label ?? "");
|
|
2597
|
-
const ref = typeof msg.ref === "string" ? msg.ref.trim() : "";
|
|
2598
|
-
const requestedSync = msg.sync === "account" || msg.sync === "node" ? msg.sync : undefined;
|
|
2599
|
-
// Persist the secret and its requested tier atomically. Creating a
|
|
2600
|
-
// machine-only credential must never leave an account-tier crash window.
|
|
2601
|
-
if (ref)
|
|
2602
|
-
await setProviderReferenceLabeled(credsDir, provider, label, ref, requestedSync);
|
|
2603
|
-
else
|
|
2604
|
-
await setProviderApiKeyLabeled(credsDir, provider, label, String(msg.key ?? ""), requestedSync);
|
|
2605
|
-
await pushModelAuthToControlPlane();
|
|
2606
|
-
await refreshSessionAfterAuth();
|
|
2607
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2608
|
-
broadcast({ type: "providers.list", providers: await listProvidersUnified() });
|
|
2609
|
-
ctx.reply({ type: "credential.set.ok", requestId: msg.requestId });
|
|
2610
|
-
}
|
|
2611
|
-
catch (error) {
|
|
2612
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2613
|
-
relay?.sendEvent({ type: "session.error", error: message });
|
|
2614
|
-
ctx.reply({ type: "credential.set.error", requestId: msg.requestId, error: message });
|
|
2615
|
-
}
|
|
2616
|
-
},
|
|
2617
|
-
async "credential.remove"(msg, ctx) {
|
|
2618
|
-
try {
|
|
2619
|
-
await removeProviderCredential(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""));
|
|
2620
|
-
await pushModelAuthToControlPlane();
|
|
2621
|
-
await refreshSessionAfterAuth();
|
|
2622
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2623
|
-
broadcast({ type: "providers.list", providers: await listProvidersUnified() });
|
|
2624
|
-
ctx.reply({ type: "credential.remove.ok", requestId: msg.requestId });
|
|
2625
|
-
}
|
|
2626
|
-
catch (error) {
|
|
2627
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2628
|
-
relay?.sendEvent({ type: "session.error", error: message });
|
|
2629
|
-
ctx.reply({ type: "credential.remove.error", requestId: msg.requestId, error: message });
|
|
2630
|
-
}
|
|
2631
|
-
},
|
|
2632
|
-
async "credential.sync.set"(msg, ctx) {
|
|
2633
|
-
try {
|
|
2634
|
-
const sync = msg.sync === "node" ? "node" : "account";
|
|
2635
|
-
await setCredentialSync(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""), sync);
|
|
2636
|
-
await pushModelAuthToControlPlane();
|
|
2637
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2638
|
-
ctx.reply({ type: "credential.sync.set.ok", requestId: msg.requestId });
|
|
2639
|
-
}
|
|
2640
|
-
catch (error) {
|
|
2641
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2642
|
-
relay?.sendEvent({ type: "session.error", error: message });
|
|
2643
|
-
ctx.reply({ type: "credential.sync.set.error", requestId: msg.requestId, error: message });
|
|
2644
|
-
}
|
|
2645
|
-
},
|
|
2646
|
-
async "credential.unattended.set"(msg, ctx) {
|
|
2647
|
-
try {
|
|
2648
|
-
await setCredentialUnattended(credsDir, String(msg.provider ?? ""), String(msg.label ?? ""), msg.unattended === true);
|
|
2649
|
-
await pushModelAuthToControlPlane();
|
|
2650
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2651
|
-
ctx.reply({ type: "credential.unattended.set.ok", requestId: msg.requestId });
|
|
2652
|
-
}
|
|
2653
|
-
catch (error) {
|
|
2654
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2655
|
-
ctx.reply({ type: "credential.unattended.set.error", requestId: msg.requestId, error: message });
|
|
2656
|
-
}
|
|
2657
|
-
},
|
|
2658
|
-
// "Test connection": a bounded, non-secret liveness probe for one credential
|
|
2659
|
-
// record (see credentials/api.ts testCredential). The reply carries only
|
|
2660
|
-
// ok/at/reason — the credential's own token never leaves this handler.
|
|
2661
|
-
async "credential.test"(msg, ctx) {
|
|
2662
|
-
const provider = String(msg.provider ?? "").trim().toLowerCase();
|
|
2663
|
-
const label = String(msg.label ?? "");
|
|
2664
|
-
try {
|
|
2665
|
-
const result = await testProviderCredential(credsDir, provider, label);
|
|
2666
|
-
ctx.reply({ type: "credential.test.result", requestId: msg.requestId, provider, label, ...result });
|
|
2667
|
-
relay?.sendEvent({ type: "credentials.records", records: await listCredentialRecords(credsDir) });
|
|
2668
|
-
}
|
|
2669
|
-
catch (error) {
|
|
2670
|
-
ctx.reply({ type: "credential.test.result", requestId: msg.requestId, provider, label, ok: false, at: Date.now(), reason: "network_error" });
|
|
2671
|
-
relay?.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
2672
|
-
}
|
|
2673
|
-
},
|
|
2674
|
-
async "credentials.presets.get"() {
|
|
2675
|
-
relay?.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
2676
|
-
},
|
|
2677
|
-
async "credentials.presets.setActive"(msg) {
|
|
2678
|
-
try {
|
|
2679
|
-
setActiveCredentialPreset(credsDir, String(msg.active ?? ""));
|
|
2680
|
-
await refreshSessionAfterAuth();
|
|
2681
|
-
relay?.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
2682
|
-
}
|
|
2683
|
-
catch (error) {
|
|
2684
|
-
relay?.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
2685
|
-
}
|
|
2686
|
-
},
|
|
2687
|
-
async "credentials.presets.setMapping"(msg, ctx) {
|
|
2688
|
-
try {
|
|
2689
|
-
setCredentialPresetMapping(credsDir, String(msg.preset ?? ""), String(msg.provider ?? ""), String(msg.label ?? ""));
|
|
2690
|
-
await refreshSessionAfterAuth();
|
|
2691
|
-
relay?.sendEvent({ type: "credentials.presets", presets: getCredentialPresets(credsDir) });
|
|
2692
|
-
ctx.reply({ type: "credentials.presets.setMapping.ok", requestId: msg.requestId });
|
|
2693
|
-
}
|
|
2694
|
-
catch (error) {
|
|
2695
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2696
|
-
ctx.reply({ type: "credentials.presets.setMapping.error", requestId: msg.requestId, error: message });
|
|
2697
|
-
}
|
|
2698
|
-
},
|
|
2699
|
-
// --- Local / custom models (Bivy-owned registry; Pi is a projection). ---
|
|
2700
|
-
async "models.custom.list"() {
|
|
2701
|
-
relay?.sendEvent({ type: "models.custom.list", providers: await localModelSummaries() });
|
|
2702
|
-
},
|
|
2703
|
-
async "models.custom.presets"() {
|
|
2704
|
-
relay?.sendEvent({ type: "models.custom.presets", presets: await localModelPresets() });
|
|
2705
|
-
},
|
|
2706
|
-
async "models.custom.discover"(_msg, ctx) {
|
|
2707
|
-
try {
|
|
2708
|
-
ctx.reply({ type: "models.custom.discover.ok", ...(await discoverModelsOnMachine()) });
|
|
2709
|
-
}
|
|
2710
|
-
catch (error) {
|
|
2711
|
-
ctx.reply({ type: "models.custom.discover.error", error: error instanceof Error ? error.message : String(error) });
|
|
2712
|
-
}
|
|
2713
|
-
},
|
|
2714
|
-
async "models.custom.verify"(msg, ctx) {
|
|
2715
|
-
try {
|
|
2716
|
-
ctx.reply({ type: "models.custom.verify.ok", result: await verifyModelEndpoint(msg) });
|
|
2717
|
-
}
|
|
2718
|
-
catch (error) {
|
|
2719
|
-
ctx.reply({ type: "models.custom.verify.error", error: error instanceof Error ? error.message : String(error) });
|
|
2720
|
-
}
|
|
2721
|
-
},
|
|
2722
|
-
async "models.custom.save"(msg, ctx) {
|
|
2723
|
-
try {
|
|
2724
|
-
// The client sends the same field set as the REST body (baseUrl, api,
|
|
2725
|
-
// apiKey, models[], compat, name, providerId). persistLocalModelSave
|
|
2726
|
-
// broadcasts the refreshed list to every client, requester included.
|
|
2727
|
-
const result = await persistLocalModelSave(msg?.spec ?? msg);
|
|
2728
|
-
// Return the normalized (Machine-scoped) provider id so the PWA can make
|
|
2729
|
-
// the imported model the next draft's explicit choice.
|
|
2730
|
-
ctx.reply({ type: "models.custom.save.ok", requestId: msg.requestId, provider: result.id });
|
|
2731
|
-
}
|
|
2732
|
-
catch (error) {
|
|
2733
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2734
|
-
relay?.sendEvent({ type: "session.error", error: message });
|
|
2735
|
-
ctx.reply({ type: "models.custom.save.error", requestId: msg.requestId, error: message });
|
|
2736
|
-
}
|
|
2737
|
-
},
|
|
2738
|
-
async "models.custom.remove"(msg) {
|
|
2739
|
-
try {
|
|
2740
|
-
await persistLocalModelRemove(String(msg.id ?? msg.provider ?? ""));
|
|
2741
|
-
}
|
|
2742
|
-
catch (error) {
|
|
2743
|
-
relay?.sendEvent({ type: "session.error", error: error instanceof Error ? error.message : String(error) });
|
|
2744
|
-
}
|
|
2745
|
-
},
|
|
2746
2605
|
// --- Rulesets (run-orchestration policy; Bivy-owned, node-local). ---
|
|
2747
2606
|
"rulesets.list"(_msg, ctx) {
|
|
2748
2607
|
ctx.reply({ type: "rulesets.list", rulesets: rulesetInfos() });
|
package/package.json
CHANGED