@sparkelf/dsh-dataops-managed 0.3.13 → 0.3.15
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/lib/client.js +367 -161
- package/lib/client.js.map +1 -1
- package/lib/index.js +270 -33
- package/lib/types/client/ManagedDataOpsSection.d.ts +2 -4
- package/lib/types/client/locales.d.ts +15 -19
- package/lib/types/client/workspace-limits-client.d.ts +36 -0
- package/lib/types/index.d.ts +37 -4
- package/lib/types/workspace-limits.d.ts +71 -0
- package/package.json +3 -3
package/lib/index.js
CHANGED
|
@@ -14,17 +14,17 @@ function modelIdOf(entry) {
|
|
|
14
14
|
return typeof id === "string" && id.trim() !== "" ? id : null;
|
|
15
15
|
}
|
|
16
16
|
/** Narrow an unknown value to a plain object. */
|
|
17
|
-
function asRecord$
|
|
17
|
+
function asRecord$4(value) {
|
|
18
18
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
19
19
|
}
|
|
20
20
|
/** Read provider profiles out of a settings value. */
|
|
21
21
|
function providersOf(value) {
|
|
22
|
-
const root = asRecord$
|
|
23
|
-
const providers = root === null ? null : asRecord$
|
|
22
|
+
const root = asRecord$4(value);
|
|
23
|
+
const providers = root === null ? null : asRecord$4(root[PROVIDERS_FIELD]);
|
|
24
24
|
if (providers === null) return {};
|
|
25
25
|
const result = {};
|
|
26
26
|
for (const [providerId, profile] of Object.entries(providers)) {
|
|
27
|
-
const narrowed = asRecord$
|
|
27
|
+
const narrowed = asRecord$4(profile);
|
|
28
28
|
if (narrowed !== null) result[providerId] = narrowed;
|
|
29
29
|
}
|
|
30
30
|
return result;
|
|
@@ -58,7 +58,7 @@ function modelIdsByProvider(providers) {
|
|
|
58
58
|
for (const [providerId, profile] of Object.entries(providers)) {
|
|
59
59
|
const models = profile[MODELS_FIELD];
|
|
60
60
|
if (!Array.isArray(models)) continue;
|
|
61
|
-
result[providerId] = models.map((entry) => asRecord$
|
|
61
|
+
result[providerId] = models.map((entry) => asRecord$4(entry)).filter((entry) => entry !== null).map((entry) => modelIdOf(entry)).filter((id) => id !== null);
|
|
62
62
|
}
|
|
63
63
|
return result;
|
|
64
64
|
}
|
|
@@ -79,7 +79,7 @@ function selectSharedModels(providers, sharedModelIds) {
|
|
|
79
79
|
const models = profile[MODELS_FIELD];
|
|
80
80
|
if (!Array.isArray(models)) continue;
|
|
81
81
|
const kept = models.filter((entry) => {
|
|
82
|
-
const narrowed = asRecord$
|
|
82
|
+
const narrowed = asRecord$4(entry);
|
|
83
83
|
const id = narrowed === null ? null : modelIdOf(narrowed);
|
|
84
84
|
return id !== null && offered.has(id);
|
|
85
85
|
});
|
|
@@ -113,9 +113,9 @@ function mergeProviders(local, adopted) {
|
|
|
113
113
|
continue;
|
|
114
114
|
}
|
|
115
115
|
const localModels = Array.isArray(localProfile[MODELS_FIELD]) ? localProfile[MODELS_FIELD] : [];
|
|
116
|
-
const localIds = new Set(localModels.map((entry) => asRecord$
|
|
116
|
+
const localIds = new Set(localModels.map((entry) => asRecord$4(entry)).filter((entry) => entry !== null).map((entry) => modelIdOf(entry)).filter((id) => id !== null));
|
|
117
117
|
const missing = adoptedModels.filter((entry) => {
|
|
118
|
-
const narrowed = asRecord$
|
|
118
|
+
const narrowed = asRecord$4(entry);
|
|
119
119
|
const id = narrowed === null ? null : modelIdOf(narrowed);
|
|
120
120
|
return id !== null && !localIds.has(id);
|
|
121
121
|
});
|
|
@@ -147,7 +147,7 @@ async function writeModels(ctx, providers, revision) {
|
|
|
147
147
|
//#endregion
|
|
148
148
|
//#region src/model-sync.ts
|
|
149
149
|
/** Narrow an unknown value to a plain object. */
|
|
150
|
-
function asRecord$
|
|
150
|
+
function asRecord$3(value) {
|
|
151
151
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
@@ -190,10 +190,10 @@ var ModelSync = class {
|
|
|
190
190
|
});
|
|
191
191
|
if (response.status === 401) return void 0;
|
|
192
192
|
if (!response.ok) throw new Error(`Shared model read failed with HTTP ${String(response.status)}`);
|
|
193
|
-
const body = asRecord$
|
|
193
|
+
const body = asRecord$3(await response.json());
|
|
194
194
|
if (body === null) return void 0;
|
|
195
|
-
const payload = asRecord$
|
|
196
|
-
const providers = payload === null ? null : asRecord$
|
|
195
|
+
const payload = asRecord$3(body.payload);
|
|
196
|
+
const providers = payload === null ? null : asRecord$3(payload.providers);
|
|
197
197
|
return {
|
|
198
198
|
sharingEnabled: body.sharingEnabled === true,
|
|
199
199
|
payload: {
|
|
@@ -257,7 +257,7 @@ var ModelSync = class {
|
|
|
257
257
|
const response = await fetch(new URL("/api/ai/shared-models/publication", this.apiBase()), { headers: { authorization: `Bearer ${token}` } });
|
|
258
258
|
if (response.status === 401) return null;
|
|
259
259
|
if (!response.ok) throw new Error(`Shared model status read failed with HTTP ${String(response.status)}`);
|
|
260
|
-
const body = asRecord$
|
|
260
|
+
const body = asRecord$3(await response.json());
|
|
261
261
|
return {
|
|
262
262
|
sharingEnabled: body?.sharingEnabled === true,
|
|
263
263
|
canPublish: body?.canPublish === true
|
|
@@ -391,7 +391,7 @@ var ModelSync = class {
|
|
|
391
391
|
/** Default state before this workspace stored anything. */
|
|
392
392
|
const SECTION_SYNC_DEFAULT = { sections: {} };
|
|
393
393
|
/** Narrow an unknown value to a plain object. */
|
|
394
|
-
function asRecord$
|
|
394
|
+
function asRecord$2(value) {
|
|
395
395
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
396
396
|
}
|
|
397
397
|
/**
|
|
@@ -430,7 +430,7 @@ var SettingsSync = class {
|
|
|
430
430
|
readSection(ns) {
|
|
431
431
|
const descriptor = this.ctx.settings.describe().find((entry) => entry.ns === ns);
|
|
432
432
|
if (descriptor === void 0) return void 0;
|
|
433
|
-
const value = asRecord$
|
|
433
|
+
const value = asRecord$2(descriptor.value);
|
|
434
434
|
if (value === null) return void 0;
|
|
435
435
|
return {
|
|
436
436
|
value,
|
|
@@ -534,7 +534,7 @@ var SettingsSync = class {
|
|
|
534
534
|
//#endregion
|
|
535
535
|
//#region src/skill-plaza.ts
|
|
536
536
|
/** Narrow an unknown value to a plain object. */
|
|
537
|
-
function asRecord(value) {
|
|
537
|
+
function asRecord$1(value) {
|
|
538
538
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
539
539
|
}
|
|
540
540
|
/**
|
|
@@ -658,7 +658,7 @@ var SkillPlaza = class {
|
|
|
658
658
|
method: "POST",
|
|
659
659
|
body: JSON.stringify({ skillId })
|
|
660
660
|
});
|
|
661
|
-
const record = body === void 0 ? null : asRecord(body);
|
|
661
|
+
const record = body === void 0 ? null : asRecord$1(body);
|
|
662
662
|
if (record === null) return null;
|
|
663
663
|
return {
|
|
664
664
|
installed: record.installed === true,
|
|
@@ -668,7 +668,7 @@ var SkillPlaza = class {
|
|
|
668
668
|
}
|
|
669
669
|
/** Project one API row into the shape the browser reads. */
|
|
670
670
|
toPlazaSkill(value) {
|
|
671
|
-
const row = asRecord(value);
|
|
671
|
+
const row = asRecord$1(value);
|
|
672
672
|
if (row === null || typeof row.id !== "number" || typeof row.name !== "string") return [];
|
|
673
673
|
return [{
|
|
674
674
|
id: row.id,
|
|
@@ -682,6 +682,119 @@ var SkillPlaza = class {
|
|
|
682
682
|
}
|
|
683
683
|
};
|
|
684
684
|
//#endregion
|
|
685
|
+
//#region src/workspace-limits.ts
|
|
686
|
+
/** Narrow a value to a plain object. */
|
|
687
|
+
function asRecord(value) {
|
|
688
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
689
|
+
}
|
|
690
|
+
/** Narrow a value to a positive integer. */
|
|
691
|
+
function asByteCount(value) {
|
|
692
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : null;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* A rejection DataOps answered with, carrying its status and message.
|
|
696
|
+
*
|
|
697
|
+
* Without this the caller can only tell that something failed, and a value DataOps refused —
|
|
698
|
+
* which the user can correct — is indistinguishable from a fault in the request path.
|
|
699
|
+
*/
|
|
700
|
+
var WorkspaceLimitsRejection = class extends Error {
|
|
701
|
+
status;
|
|
702
|
+
constructor(status, message) {
|
|
703
|
+
super(message);
|
|
704
|
+
this.status = status;
|
|
705
|
+
this.name = "WorkspaceLimitsRejection";
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
/** Read DataOps' own explanation from a failed response, falling back to the status line. */
|
|
709
|
+
async function rejectionMessage(response, fallback) {
|
|
710
|
+
try {
|
|
711
|
+
const message = asRecord(await response.json())?.message;
|
|
712
|
+
if (typeof message === "string" && message.trim() !== "") return message;
|
|
713
|
+
if (Array.isArray(message) && typeof message[0] === "string") return message[0];
|
|
714
|
+
} catch {}
|
|
715
|
+
return `${fallback} with HTTP ${String(response.status)}`;
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* The workspace limits an administrator sets in DataOps.
|
|
719
|
+
*
|
|
720
|
+
* The plugin holds the DataOps JWT, so the browser cannot read these itself; this class is the
|
|
721
|
+
* only path from the Settings panel to that document.
|
|
722
|
+
*/
|
|
723
|
+
var WorkspaceLimits = class {
|
|
724
|
+
ctx;
|
|
725
|
+
config;
|
|
726
|
+
constructor(ctx, config) {
|
|
727
|
+
this.ctx = ctx;
|
|
728
|
+
this.config = config;
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Read the DataOps JWT this workspace is currently authorized with.
|
|
732
|
+
* @returns The token, or undefined when the workspace has no session yet.
|
|
733
|
+
*/
|
|
734
|
+
async token() {
|
|
735
|
+
return (await this.ctx.credentials.resolve(credentialRef(this.config.credentialRef)))?.value;
|
|
736
|
+
}
|
|
737
|
+
/** The DataOps API origin this workspace reports to. */
|
|
738
|
+
apiBase() {
|
|
739
|
+
return new URL(this.config.baseUrl).origin;
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Read the current ceilings.
|
|
743
|
+
* @returns The status, or undefined when the workspace is not authorized.
|
|
744
|
+
*/
|
|
745
|
+
async status() {
|
|
746
|
+
const token = await this.token();
|
|
747
|
+
if (token === void 0) return void 0;
|
|
748
|
+
const response = await fetch(new URL("/api/ai/attachment-settings", this.apiBase()), { headers: { authorization: `Bearer ${token}` } });
|
|
749
|
+
if (response.status === 401) return void 0;
|
|
750
|
+
if (!response.ok) throw new WorkspaceLimitsRejection(response.status, await rejectionMessage(response, "Workspace limits read failed"));
|
|
751
|
+
const body = asRecord(await response.json());
|
|
752
|
+
const settings = body === null ? null : asRecord(body.settings);
|
|
753
|
+
const size = settings === null ? null : asRecord(settings.workspaceSize);
|
|
754
|
+
const fileReadMaxBytes = size === null ? null : asByteCount(size.fileReadMaxBytes);
|
|
755
|
+
const uploadMaxBytes = size === null ? null : asByteCount(size.uploadMaxBytes);
|
|
756
|
+
if (fileReadMaxBytes === null || uploadMaxBytes === null) throw new Error("Workspace limits read returned an unrecognized document");
|
|
757
|
+
return {
|
|
758
|
+
limits: {
|
|
759
|
+
fileReadMaxBytes,
|
|
760
|
+
uploadMaxBytes
|
|
761
|
+
},
|
|
762
|
+
canWrite: true,
|
|
763
|
+
readOnlyReason: null
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Write new ceilings.
|
|
768
|
+
* @param limits - The ceilings to store.
|
|
769
|
+
* @returns The stored status.
|
|
770
|
+
*/
|
|
771
|
+
async save(limits) {
|
|
772
|
+
const token = await this.token();
|
|
773
|
+
if (token === void 0) return void 0;
|
|
774
|
+
const response = await fetch(new URL("/api/ai/attachment-settings", this.apiBase()), {
|
|
775
|
+
method: "PATCH",
|
|
776
|
+
headers: {
|
|
777
|
+
authorization: `Bearer ${token}`,
|
|
778
|
+
"content-type": "application/json"
|
|
779
|
+
},
|
|
780
|
+
body: JSON.stringify({ workspaceSize: limits })
|
|
781
|
+
});
|
|
782
|
+
if (response.status === 401) return void 0;
|
|
783
|
+
if (!response.ok) throw new WorkspaceLimitsRejection(response.status, await rejectionMessage(response, "Workspace limits write failed"));
|
|
784
|
+
return this.status();
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
/** The live instance, so the Settings route can act on it after activation. */
|
|
788
|
+
let limitsRef;
|
|
789
|
+
/** @param next - Instance to publish, or undefined to clear. */
|
|
790
|
+
function setWorkspaceLimits(next) {
|
|
791
|
+
limitsRef = next;
|
|
792
|
+
}
|
|
793
|
+
/** @returns The live workspace-limits instance, when the plugin activated. */
|
|
794
|
+
function currentWorkspaceLimits() {
|
|
795
|
+
return limitsRef;
|
|
796
|
+
}
|
|
797
|
+
//#endregion
|
|
685
798
|
//#region src/index.ts
|
|
686
799
|
/** Cordis plugin name for the DataOps-managed integration. */
|
|
687
800
|
const name = "dataops-managed";
|
|
@@ -700,6 +813,8 @@ const MANAGED_PLUGIN_ID = "dataops-managed";
|
|
|
700
813
|
const MODEL_SYNC_PATH = "/integrations/dataops/model-sync";
|
|
701
814
|
/** Same-origin route the skill center uses to read and change plaza sharing. */
|
|
702
815
|
const SKILL_PLAZA_PATH = "/integrations/dataops/skill-plaza";
|
|
816
|
+
/** Same-origin route the Settings panel uses to read and change this workspace's limits. */
|
|
817
|
+
const WORKSPACE_LIMITS_PATH = "/integrations/dataops/workspace-limits";
|
|
703
818
|
/** The live sync instance, so the settings routes can act on it after activation. */
|
|
704
819
|
let modelSyncRef;
|
|
705
820
|
/** The live plaza instance, so the skill center routes can act on it after activation. */
|
|
@@ -718,21 +833,54 @@ let settingsSyncRef;
|
|
|
718
833
|
function currentSettingsSync() {
|
|
719
834
|
return settingsSyncRef;
|
|
720
835
|
}
|
|
836
|
+
/**
|
|
837
|
+
* Read the value behind a possibly-volatile field.
|
|
838
|
+
*
|
|
839
|
+
* A volatile field always arrives as a reference, but the plain shape is accepted too so a
|
|
840
|
+
* caller that validated the config without a settings service still reads its value.
|
|
841
|
+
* @param field - The config field as the runtime supplied it.
|
|
842
|
+
* @param fallback - Value to use when nothing is stored yet.
|
|
843
|
+
* @returns The current value.
|
|
844
|
+
*/
|
|
845
|
+
function volatileValue(field, fallback) {
|
|
846
|
+
if (field === void 0) return fallback;
|
|
847
|
+
if (typeof field === "object" && field !== null && "get" in field && typeof field.get === "function") return field.get() ?? fallback;
|
|
848
|
+
return field;
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* Per-tool-call timeout a workspace starts with.
|
|
852
|
+
*
|
|
853
|
+
* Named rather than inlined so the schema default and the read fallback cannot drift: the field is
|
|
854
|
+
* volatile, so a workspace that never changed it reads no value at all and both places have to
|
|
855
|
+
* answer the same number.
|
|
856
|
+
*/
|
|
857
|
+
const DEFAULT_TOOL_CALL_TIMEOUT_MS = 3e5;
|
|
858
|
+
/** Default sync state before this workspace stored anything. */
|
|
859
|
+
const MODEL_SYNC_DEFAULT = {
|
|
860
|
+
detached: false,
|
|
861
|
+
adoptedModelIds: [],
|
|
862
|
+
privateModelIds: []
|
|
863
|
+
};
|
|
721
864
|
/** Schemastery parser for managed DataOps configuration. */
|
|
722
865
|
const Config = z.object({
|
|
723
866
|
baseUrl: z.string().default("http://host.docker.internal:3101"),
|
|
724
867
|
serverName: z.string().default("dataops"),
|
|
725
868
|
credentialRef: z.string().role("credential-ref").default("DATAOPS_ACCESS_TOKEN"),
|
|
726
|
-
|
|
869
|
+
/**
|
|
870
|
+
* Volatile so the settings service persists it in this workspace's document: the timeout an
|
|
871
|
+
* operator needs depends on the query behind a tool, which is deployment knowledge the profile
|
|
872
|
+
* that mounts this plugin does not have.
|
|
873
|
+
*
|
|
874
|
+
* `z.any()` like the sync state above, and for the same reason: a volatile field is delivered
|
|
875
|
+
* to `apply` as a reference rather than a value, so a narrowed schema would describe a shape
|
|
876
|
+
* the runtime does not pass. The range is enforced where the value is read.
|
|
877
|
+
*/
|
|
878
|
+
toolCallTimeoutMs: z.any().default(DEFAULT_TOOL_CALL_TIMEOUT_MS).volatile(),
|
|
727
879
|
/**
|
|
728
880
|
* Volatile so the settings service persists it in this workspace's document. The shape is
|
|
729
881
|
* left open because the sync state is this plugin's own record, not user configuration.
|
|
730
882
|
*/
|
|
731
|
-
modelSync: z.any().default(
|
|
732
|
-
detached: false,
|
|
733
|
-
adoptedModelIds: [],
|
|
734
|
-
privateModelIds: []
|
|
735
|
-
}).volatile(),
|
|
883
|
+
modelSync: z.any().default(MODEL_SYNC_DEFAULT).volatile(),
|
|
736
884
|
/** Volatile for the same reason as `modelSync`: it is this plugin's own record. */
|
|
737
885
|
settingsSync: z.any().default(SECTION_SYNC_DEFAULT).volatile(),
|
|
738
886
|
/**
|
|
@@ -765,7 +913,7 @@ async function apply(ctx, config) {
|
|
|
765
913
|
url: `${baseUrl}/api/ai/data-query/mcp`,
|
|
766
914
|
headers: {},
|
|
767
915
|
bearerTokenRef: config.credentialRef,
|
|
768
|
-
toolCallTimeoutMs: config.toolCallTimeoutMs
|
|
916
|
+
toolCallTimeoutMs: volatileValue(config.toolCallTimeoutMs, DEFAULT_TOOL_CALL_TIMEOUT_MS)
|
|
769
917
|
});
|
|
770
918
|
mcpFiber = fiber;
|
|
771
919
|
try {
|
|
@@ -781,23 +929,47 @@ async function apply(ctx, config) {
|
|
|
781
929
|
mcpFiber = void 0;
|
|
782
930
|
if (fiber !== void 0 && fiber.uid !== null) await fiber.dispose();
|
|
783
931
|
}, "dataops-managed: MCP lifecycle");
|
|
784
|
-
|
|
932
|
+
/**
|
|
933
|
+
* Connect the MCP client when a JWT is already stored.
|
|
934
|
+
*
|
|
935
|
+
* A connect can fail for reasons this plugin does not own — the stored JWT may have expired,
|
|
936
|
+
* or DataOps may be unreachable at this moment — and those are states the settings routes
|
|
937
|
+
* report and recover from. Awaiting it here would abort activation, leaving the plugin with
|
|
938
|
+
* no routes at all: the panel then cannot read the very state that explains the failure, and
|
|
939
|
+
* the browser reports a connection error instead. The routes below are registered first, and
|
|
940
|
+
* a failed connect is reported rather than thrown.
|
|
941
|
+
*/
|
|
942
|
+
const connectExisting = async () => {
|
|
943
|
+
if (await ctx.credentials.resolve(accessRef) === void 0) return;
|
|
944
|
+
try {
|
|
945
|
+
await ensureMcp();
|
|
946
|
+
} catch (error) {
|
|
947
|
+
ctx.logger.warn("dataops-managed: connecting the DataOps MCP server failed; the settings routes stay up so a new JWT can be connected");
|
|
948
|
+
ctx.logger.warn(error);
|
|
949
|
+
}
|
|
950
|
+
};
|
|
785
951
|
/**
|
|
786
952
|
* The sync state lives in this plugin's own Config, so the settings service persists it in
|
|
787
953
|
* the workspace document and it survives a restart.
|
|
788
954
|
*/
|
|
789
955
|
const syncStore = {
|
|
790
|
-
read: () =>
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
956
|
+
read: () => {
|
|
957
|
+
const state = volatileValue(config.modelSync, MODEL_SYNC_DEFAULT);
|
|
958
|
+
return {
|
|
959
|
+
detached: state?.detached === true,
|
|
960
|
+
adoptedModelIds: Array.isArray(state?.adoptedModelIds) ? state.adoptedModelIds : [],
|
|
961
|
+
privateModelIds: Array.isArray(state?.privateModelIds) ? state.privateModelIds : []
|
|
962
|
+
};
|
|
963
|
+
},
|
|
795
964
|
write: async (next) => {
|
|
796
965
|
await ctx.settings.update(MANAGED_PLUGIN_ID, { modelSync: next });
|
|
797
966
|
}
|
|
798
967
|
};
|
|
799
968
|
const settingsSync = new SettingsSync(ctx, {
|
|
800
|
-
read: () =>
|
|
969
|
+
read: () => {
|
|
970
|
+
const state = volatileValue(config.settingsSync, SECTION_SYNC_DEFAULT);
|
|
971
|
+
return { sections: typeof state?.sections === "object" && state.sections !== null ? state.sections : {} };
|
|
972
|
+
},
|
|
801
973
|
write: async (next) => {
|
|
802
974
|
await ctx.settings.update(MANAGED_PLUGIN_ID, { settingsSync: next });
|
|
803
975
|
}
|
|
@@ -843,6 +1015,10 @@ async function apply(ctx, config) {
|
|
|
843
1015
|
baseUrl,
|
|
844
1016
|
credentialRef: config.credentialRef
|
|
845
1017
|
});
|
|
1018
|
+
setWorkspaceLimits(new WorkspaceLimits(ctx, {
|
|
1019
|
+
baseUrl,
|
|
1020
|
+
credentialRef: config.credentialRef
|
|
1021
|
+
}));
|
|
846
1022
|
/**
|
|
847
1023
|
* Adopt the publisher's models once the workspace has a session.
|
|
848
1024
|
*
|
|
@@ -1052,6 +1228,67 @@ async function apply(ctx, config) {
|
|
|
1052
1228
|
}
|
|
1053
1229
|
}
|
|
1054
1230
|
}), "dataops-managed: skill plaza routes");
|
|
1231
|
+
/**
|
|
1232
|
+
* The workspace limits an administrator sets in DataOps.
|
|
1233
|
+
*
|
|
1234
|
+
* Read through the plugin because it holds the DataOps JWT; the page cannot reach those settings
|
|
1235
|
+
* itself. Same-origin only, like its neighbours.
|
|
1236
|
+
*/
|
|
1237
|
+
ctx.effect(() => ctx.webServer.register({
|
|
1238
|
+
kind: "exact",
|
|
1239
|
+
path: WORKSPACE_LIMITS_PATH,
|
|
1240
|
+
handler: async (request, response) => {
|
|
1241
|
+
const limits = currentWorkspaceLimits();
|
|
1242
|
+
if (limits === void 0) {
|
|
1243
|
+
writeJson(response, 503, { error: "workspace limits are not active" });
|
|
1244
|
+
return;
|
|
1245
|
+
}
|
|
1246
|
+
try {
|
|
1247
|
+
if (request.method === "GET") {
|
|
1248
|
+
const status = await limits.status();
|
|
1249
|
+
if (status === void 0) {
|
|
1250
|
+
writeJson(response, 503, { error: "workspace limits are not active" });
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
writeJson(response, 200, status);
|
|
1254
|
+
return;
|
|
1255
|
+
}
|
|
1256
|
+
if (request.method !== "POST") {
|
|
1257
|
+
response.writeHead(405, {
|
|
1258
|
+
allow: "GET, POST",
|
|
1259
|
+
"content-length": "0"
|
|
1260
|
+
});
|
|
1261
|
+
response.end();
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1264
|
+
const body = await readJson(request);
|
|
1265
|
+
const fileReadMaxBytes = body?.fileReadMaxBytes;
|
|
1266
|
+
const uploadMaxBytes = body?.uploadMaxBytes;
|
|
1267
|
+
if (typeof fileReadMaxBytes !== "number" || typeof uploadMaxBytes !== "number") {
|
|
1268
|
+
writeJson(response, 400, { error: "both byte ceilings are required" });
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
const status = await limits.save({
|
|
1272
|
+
fileReadMaxBytes,
|
|
1273
|
+
uploadMaxBytes
|
|
1274
|
+
});
|
|
1275
|
+
if (status === void 0) {
|
|
1276
|
+
writeJson(response, 503, { error: "workspace limits are not active" });
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
writeJson(response, 200, status);
|
|
1280
|
+
} catch (error) {
|
|
1281
|
+
if (error instanceof WorkspaceLimitsRejection) {
|
|
1282
|
+
writeJson(response, error.status, { error: error.message });
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
ctx.logger.warn("dataops-managed: workspace limits request failed");
|
|
1286
|
+
ctx.logger.warn(error);
|
|
1287
|
+
writeJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
}), "dataops-managed: workspace limits route");
|
|
1291
|
+
await connectExisting();
|
|
1055
1292
|
}
|
|
1056
1293
|
/** Read and parse a JSON request body, tolerating an absent one. */
|
|
1057
1294
|
async function readJson(request) {
|
|
@@ -1075,4 +1312,4 @@ function writeJson(response, status, body) {
|
|
|
1075
1312
|
response.end(payload);
|
|
1076
1313
|
}
|
|
1077
1314
|
//#endregion
|
|
1078
|
-
export { Config, MANAGED_AUTH_PATH, MANAGED_PLUGIN_ID, MODEL_SYNC_PATH, SKILL_PLAZA_PATH, apply, currentModelSync, currentSettingsSync, currentSkillPlaza, inject, name };
|
|
1315
|
+
export { Config, DEFAULT_TOOL_CALL_TIMEOUT_MS, MANAGED_AUTH_PATH, MANAGED_PLUGIN_ID, MODEL_SYNC_PATH, SKILL_PLAZA_PATH, WORKSPACE_LIMITS_PATH, apply, currentModelSync, currentSettingsSync, currentSkillPlaza, currentWorkspaceLimits, inject, name };
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
|
|
2
2
|
import type { en } from './locales.ts';
|
|
3
|
-
import { MODEL_SYNC_PATH } from './model-sync-client.ts';
|
|
4
3
|
/** Values injected by the DSH Settings slot. */
|
|
5
4
|
export interface ManagedDataOpsSectionInjected {
|
|
6
5
|
/** Translate one managed DataOps Settings message key. */
|
|
@@ -9,10 +8,9 @@ export interface ManagedDataOpsSectionInjected {
|
|
|
9
8
|
/** Props accepted by the managed DataOps Settings section. */
|
|
10
9
|
export type ManagedDataOpsSectionProps = Partial<InjectFace<ManagedDataOpsSectionInjected>>;
|
|
11
10
|
/**
|
|
12
|
-
*
|
|
11
|
+
* The DataOps settings this workspace owns: its connection state, its branding, its file ceilings,
|
|
12
|
+
* and the default configuration it publishes to other workspaces.
|
|
13
13
|
* @param props - Settings slot injection values.
|
|
14
14
|
* @returns The localized managed DataOps section, or nothing before injection.
|
|
15
15
|
*/
|
|
16
16
|
export declare function ManagedDataOpsSection(props: ManagedDataOpsSectionProps): import("react").JSX.Element | null;
|
|
17
|
-
/** Re-exported so the settings entry can be discovered from the section alone. */
|
|
18
|
-
export { MODEL_SYNC_PATH };
|
|
@@ -2,27 +2,23 @@
|
|
|
2
2
|
export declare const en: {
|
|
3
3
|
readonly nav: "DataOps";
|
|
4
4
|
readonly title: "DataOps";
|
|
5
|
-
readonly managed: "
|
|
6
|
-
readonly
|
|
7
|
-
readonly
|
|
8
|
-
readonly
|
|
9
|
-
readonly
|
|
10
|
-
readonly
|
|
11
|
-
readonly
|
|
12
|
-
readonly
|
|
13
|
-
readonly
|
|
14
|
-
readonly
|
|
15
|
-
readonly
|
|
16
|
-
readonly modelsUnavailable: "Model configuration is unavailable until this workspace is connected to DataOps.";
|
|
5
|
+
readonly managed: "Connected to DataOps";
|
|
6
|
+
readonly unavailable: "DataOps is not connected.";
|
|
7
|
+
readonly loadFailed: "Loading failed. Try again.";
|
|
8
|
+
readonly brandingTitle: "Wanxiang branding";
|
|
9
|
+
readonly brandingToggle: "Wanxiang branding";
|
|
10
|
+
readonly limitsTitle: "File size limits";
|
|
11
|
+
readonly limitsFileReadLabel: "Largest file to read (MiB)";
|
|
12
|
+
readonly limitsUploadLabel: "Largest file to upload (MiB)";
|
|
13
|
+
readonly limitsInvalid: "Enter a size in MiB.";
|
|
14
|
+
readonly limitsSave: "Save";
|
|
15
|
+
readonly limitsSaving: "Saving…";
|
|
17
16
|
readonly defaultsTitle: "Default configuration";
|
|
18
|
-
readonly
|
|
19
|
-
readonly defaultsActionTitle: "Publish as the default";
|
|
20
|
-
readonly defaultsNotAdmin: "Only an administrator may publish the default configuration.";
|
|
21
|
-
readonly defaultsNeverPublished: "Not published yet.";
|
|
22
|
-
readonly defaultsPublishedAt: "Last published:";
|
|
23
|
-
readonly defaultsPublish: "Publish default configuration";
|
|
17
|
+
readonly defaultsPublish: "Publish";
|
|
24
18
|
readonly defaultsPublishing: "Publishing…";
|
|
25
|
-
readonly
|
|
19
|
+
readonly defaultsPublishedAt: "Last published";
|
|
20
|
+
readonly defaultsNeverPublished: "Not published yet";
|
|
21
|
+
readonly defaultsNotAdmin: "Administrators only";
|
|
26
22
|
readonly plazaTitle: "Skill plaza";
|
|
27
23
|
readonly plazaIntro: "Skills other users shared. Install one to use it in this workspace, or share a skill of your own.";
|
|
28
24
|
readonly plazaSearch: "Search skills";
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Same-origin workspace-limits calls from the DataOps settings surface. */
|
|
2
|
+
/** Route the plugin's Host half serves for workspace limits. */
|
|
3
|
+
export declare const WORKSPACE_LIMITS_PATH = "/integrations/dataops/workspace-limits";
|
|
4
|
+
/** The two byte ceilings, as the panel edits them. */
|
|
5
|
+
export interface WorkspaceLimits {
|
|
6
|
+
/** Largest file a sidebar download or agent read may carry. */
|
|
7
|
+
fileReadMaxBytes: number;
|
|
8
|
+
/** Largest file an upload may carry. */
|
|
9
|
+
uploadMaxBytes: number;
|
|
10
|
+
}
|
|
11
|
+
/** Limits as this workspace currently holds them. */
|
|
12
|
+
export interface WorkspaceLimitsStatus {
|
|
13
|
+
limits: WorkspaceLimits;
|
|
14
|
+
canWrite: boolean;
|
|
15
|
+
readOnlyReason: string | null;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* A write DataOps refused, carrying the reason it gave.
|
|
19
|
+
*
|
|
20
|
+
* The message is written for the person who set the value, so the panel shows it as-is rather
|
|
21
|
+
* than replacing it with a generic failure notice.
|
|
22
|
+
*/
|
|
23
|
+
export declare class WorkspaceLimitsRejection extends Error {
|
|
24
|
+
constructor(message: string);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Read the workspace limits the panel shows.
|
|
28
|
+
* @returns The status, or null when the workspace is not connected to DataOps.
|
|
29
|
+
*/
|
|
30
|
+
export declare function fetchWorkspaceLimits(): Promise<WorkspaceLimitsStatus | null>;
|
|
31
|
+
/**
|
|
32
|
+
* Store new workspace limits.
|
|
33
|
+
* @param limits - The ceilings to store.
|
|
34
|
+
* @returns The stored status.
|
|
35
|
+
*/
|
|
36
|
+
export declare function saveWorkspaceLimits(limits: WorkspaceLimits): Promise<WorkspaceLimitsStatus | null>;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import z from '@deepseek-ai/schemastery';
|
|
|
4
4
|
import { ModelSync } from './model-sync.ts';
|
|
5
5
|
import { SettingsSync, type SectionSyncState } from './settings-sync.ts';
|
|
6
6
|
import { SkillPlaza } from './skill-plaza.ts';
|
|
7
|
+
import { currentWorkspaceLimits } from './workspace-limits.ts';
|
|
7
8
|
/** Cordis plugin name for the DataOps-managed integration. */
|
|
8
9
|
export declare const name = "dataops-managed";
|
|
9
10
|
/** Host services required by managed JWT and MCP ownership. */
|
|
@@ -15,12 +16,27 @@ export declare const MANAGED_PLUGIN_ID = "dataops-managed";
|
|
|
15
16
|
export declare const MODEL_SYNC_PATH = "/integrations/dataops/model-sync";
|
|
16
17
|
/** Same-origin route the skill center uses to read and change plaza sharing. */
|
|
17
18
|
export declare const SKILL_PLAZA_PATH = "/integrations/dataops/skill-plaza";
|
|
19
|
+
/** Same-origin route the Settings panel uses to read and change this workspace's limits. */
|
|
20
|
+
export declare const WORKSPACE_LIMITS_PATH = "/integrations/dataops/workspace-limits";
|
|
18
21
|
/** The live skill plaza, once this plugin activated. */
|
|
19
22
|
export declare function currentSkillPlaza(): SkillPlaza | undefined;
|
|
20
23
|
/** The live model-sync instance, once this plugin activated. */
|
|
21
24
|
export declare function currentModelSync(): ModelSync | undefined;
|
|
25
|
+
export { currentWorkspaceLimits };
|
|
22
26
|
/** @returns The live instance, or undefined before this plugin activated. */
|
|
23
27
|
export declare function currentSettingsSync(): SettingsSync | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* A volatile config reference.
|
|
30
|
+
*
|
|
31
|
+
* A field marked volatile is not stored with the plugin's own configuration: the settings
|
|
32
|
+
* service keeps it in this workspace's document and hands the plugin a reference that it
|
|
33
|
+
* updates in place, so a value written through the Settings panel is visible without a
|
|
34
|
+
* restart. Reading the field directly therefore reads the reference, not the value.
|
|
35
|
+
*/
|
|
36
|
+
interface VolatileRef<T> {
|
|
37
|
+
/** The current value behind the reference. */
|
|
38
|
+
get(): T;
|
|
39
|
+
}
|
|
24
40
|
/** Configuration for the DataOps-managed MCP connection. */
|
|
25
41
|
export interface Config {
|
|
26
42
|
/** DataOps browser/API origin reachable from the DSH Host. */
|
|
@@ -29,23 +45,32 @@ export interface Config {
|
|
|
29
45
|
serverName: string;
|
|
30
46
|
/** DSH credential reference that stores the current DataOps access JWT. */
|
|
31
47
|
credentialRef: string;
|
|
32
|
-
/**
|
|
33
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Per-tool-call timeout forwarded to the MCP client.
|
|
50
|
+
*
|
|
51
|
+
* Volatile, so the settings service stores the platform default in this workspace's document
|
|
52
|
+
* and the panel can raise it without a profile edit. The read goes through `volatileValue`.
|
|
53
|
+
*/
|
|
54
|
+
toolCallTimeoutMs: number | VolatileRef<number>;
|
|
34
55
|
/**
|
|
35
56
|
* Model-sync state this workspace keeps.
|
|
36
57
|
*
|
|
37
58
|
* Volatile, so the settings service stores it in this workspace's own document. It belongs
|
|
38
59
|
* to the workspace rather than to DataOps because it records whether the user here wrote
|
|
39
60
|
* their own models, which is a fact about this workspace.
|
|
61
|
+
*
|
|
62
|
+
* Volatile, so the runtime supplies a reference and the read goes through `volatileValue`.
|
|
40
63
|
*/
|
|
41
|
-
modelSync: ModelSyncState
|
|
64
|
+
modelSync: ModelSyncState | VolatileRef<ModelSyncState>;
|
|
42
65
|
/**
|
|
43
66
|
* Settings-distribution state this workspace keeps.
|
|
44
67
|
*
|
|
45
68
|
* Volatile, like the model-sync state beside it, and for the same reason: it records whether
|
|
46
69
|
* the user here wrote their own values, which is a fact about this workspace.
|
|
70
|
+
*
|
|
71
|
+
* Volatile, like the field above, so the read goes through `volatileValue`.
|
|
47
72
|
*/
|
|
48
|
-
settingsSync: SectionSyncState
|
|
73
|
+
settingsSync: SectionSyncState | VolatileRef<SectionSyncState>;
|
|
49
74
|
/**
|
|
50
75
|
* Settings namespaces this deployment distributes to other workspaces.
|
|
51
76
|
*
|
|
@@ -63,6 +88,14 @@ export interface ModelSyncState {
|
|
|
63
88
|
/** Model ids the administrator keeps private; every other model is offered by default. */
|
|
64
89
|
privateModelIds: string[];
|
|
65
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* Per-tool-call timeout a workspace starts with.
|
|
93
|
+
*
|
|
94
|
+
* Named rather than inlined so the schema default and the read fallback cannot drift: the field is
|
|
95
|
+
* volatile, so a workspace that never changed it reads no value at all and both places have to
|
|
96
|
+
* answer the same number.
|
|
97
|
+
*/
|
|
98
|
+
export declare const DEFAULT_TOOL_CALL_TIMEOUT_MS = 300000;
|
|
66
99
|
/** Schemastery parser for managed DataOps configuration. */
|
|
67
100
|
export declare const Config: z<Config>;
|
|
68
101
|
/**
|