@sparkelf/dsh-dataops-managed 0.3.21 → 0.3.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/lib/client.js +261 -227
- package/lib/client.js.map +1 -1
- package/lib/index.js +1190 -407
- package/lib/types/api-docs.d.ts +80 -0
- package/lib/types/auth-state.d.ts +13 -0
- package/lib/types/client/ReloginPrompt.d.ts +3 -5
- package/lib/types/client/SkillPlazaSection.d.ts +33 -0
- package/lib/types/client/locales.d.ts +13 -10
- package/lib/types/connector-state.d.ts +51 -0
- package/lib/types/tool-manager.d.ts +81 -0
- package/lib/types/tool-output.d.ts +25 -0
- package/lib/types/tool-scope.d.ts +156 -0
- package/lib/types/tools.d.ts +27 -7
- package/package.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact search over the DataOps OpenAPI document.
|
|
3
|
+
*
|
|
4
|
+
* The document can be tens of thousands of lines; a model needs the operation that answers its
|
|
5
|
+
* question, not the whole contract. This module reads `paths` and returns only the fields that
|
|
6
|
+
* identify and parametrize a matching operation: method, path, operationId, summary, tags,
|
|
7
|
+
* parameters, request-body shape and security. It never executes anything and never accepts a
|
|
8
|
+
* URL: the caller names the fixed `/api/openapi.json` document, and the search stays inside it.
|
|
9
|
+
*
|
|
10
|
+
* @module api-docs
|
|
11
|
+
*/
|
|
12
|
+
/** JSON-safe content a tool may return. */
|
|
13
|
+
type Json = null | boolean | number | string | Json[] | {
|
|
14
|
+
[key: string]: Json;
|
|
15
|
+
};
|
|
16
|
+
/** One compact parameter row. */
|
|
17
|
+
export interface ApiDocParameter {
|
|
18
|
+
name: string;
|
|
19
|
+
in: string;
|
|
20
|
+
required: boolean;
|
|
21
|
+
type: string;
|
|
22
|
+
}
|
|
23
|
+
/** A compact request-body description. */
|
|
24
|
+
export interface ApiDocRequestBody {
|
|
25
|
+
required: boolean;
|
|
26
|
+
contentTypes: string[];
|
|
27
|
+
schema: ApiDocSchemaSummary | null;
|
|
28
|
+
}
|
|
29
|
+
/** A compact schema projection: enough to see the fields a call needs. */
|
|
30
|
+
export interface ApiDocSchemaSummary {
|
|
31
|
+
type?: string;
|
|
32
|
+
ref?: string;
|
|
33
|
+
required?: string[];
|
|
34
|
+
properties?: Array<{
|
|
35
|
+
name: string;
|
|
36
|
+
type: string;
|
|
37
|
+
required: boolean;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
/** One operation as the tool returns it. */
|
|
41
|
+
export interface ApiDocOperation {
|
|
42
|
+
method: string;
|
|
43
|
+
path: string;
|
|
44
|
+
operationId: string | null;
|
|
45
|
+
summary: string | null;
|
|
46
|
+
tags: string[];
|
|
47
|
+
parameters: ApiDocParameter[];
|
|
48
|
+
requestBody: ApiDocRequestBody | null;
|
|
49
|
+
security: Json[];
|
|
50
|
+
}
|
|
51
|
+
/** Everything the tool needs to report one search. */
|
|
52
|
+
export interface ApiDocsSearchResult {
|
|
53
|
+
title: string | null;
|
|
54
|
+
version: string | null;
|
|
55
|
+
openapi: string | null;
|
|
56
|
+
total: number;
|
|
57
|
+
returned: number;
|
|
58
|
+
truncated: boolean;
|
|
59
|
+
results: ApiDocOperation[];
|
|
60
|
+
}
|
|
61
|
+
/** Filters accepted by {@link searchOpenApiDocument}. */
|
|
62
|
+
export interface ApiDocsFilters {
|
|
63
|
+
query?: string;
|
|
64
|
+
tag?: string;
|
|
65
|
+
path?: string;
|
|
66
|
+
operation_id?: string;
|
|
67
|
+
method?: string;
|
|
68
|
+
}
|
|
69
|
+
/** Raised when the upstream body is not an OpenAPI document this module can search. */
|
|
70
|
+
export declare class ApiDocsUnavailableError extends Error {
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Search one parsed OpenAPI document.
|
|
74
|
+
* @param document - Parsed `/api/openapi.json` body.
|
|
75
|
+
* @param filters - Keyword, tag, path, operationId and method filters.
|
|
76
|
+
* @returns Matching operations, bounded and ordered by relevance.
|
|
77
|
+
* @throws ApiDocsUnavailableError when the document has no searchable `paths` object.
|
|
78
|
+
*/
|
|
79
|
+
export declare function searchOpenApiDocument(document: unknown, filters?: ApiDocsFilters): ApiDocsSearchResult;
|
|
80
|
+
export {};
|
|
@@ -16,6 +16,19 @@
|
|
|
16
16
|
*/
|
|
17
17
|
/** What the last DataOps answer said about the stored credential. */
|
|
18
18
|
export type AuthState = 'unknown' | 'valid' | 'expired';
|
|
19
|
+
/** One observer of the recorded credential observation. */
|
|
20
|
+
export type AuthStateListener = (state: AuthState) => void;
|
|
21
|
+
/**
|
|
22
|
+
* Observe every recorded transition.
|
|
23
|
+
*
|
|
24
|
+
* The tool manager uses this to drop its disclosure as soon as any DataOps call reports a 401:
|
|
25
|
+
* the scope that produced the tool list can no longer be trusted, and waiting for another
|
|
26
|
+
* explicit sync would leave tools offered against an ended session. Listener failures are
|
|
27
|
+
* contained so a broken observer cannot turn a credential observation into a failed call.
|
|
28
|
+
* @param listener - Called with the new state after it is recorded.
|
|
29
|
+
* @returns The exact unsubscriber.
|
|
30
|
+
*/
|
|
31
|
+
export declare const onAuthStateChange: (listener: AuthStateListener) => (() => void);
|
|
19
32
|
/**
|
|
20
33
|
* Record that a DataOps call was accepted.
|
|
21
34
|
*
|
|
@@ -7,11 +7,9 @@
|
|
|
7
7
|
* signed in any more. This prompt names that state once, with the one action that clears it.
|
|
8
8
|
*
|
|
9
9
|
* It reports and nothing more. It does not retry, expire, or re-authenticate — the portal owns
|
|
10
|
-
* the login form and is the only thing that can issue a session.
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* URL from inside here would open the wrong page on some deployments, so the dialog tells the
|
|
14
|
-
* operator which window to switch to instead of pretending it can take them.
|
|
10
|
+
* the login form and is the only thing that can issue a session. Its action navigates the parent
|
|
11
|
+
* portal when the frame's ancestor origin is known, and opens the portal separately when the
|
|
12
|
+
* browser blocks top-level navigation from the frame.
|
|
15
13
|
*
|
|
16
14
|
* @module ReloginPrompt
|
|
17
15
|
*/
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
2
|
import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
3
|
import type { en } from './locales.ts';
|
|
4
|
+
import { type PlazaSkill } from './skill-plaza-client.ts';
|
|
4
5
|
/** Values the skill center passes to a contributed section. */
|
|
5
6
|
export interface SkillPlazaSectionInjected {
|
|
6
7
|
/** Translate one DataOps plaza message key. */
|
|
@@ -16,6 +17,38 @@ export interface SkillPlazaSectionInjected {
|
|
|
16
17
|
}
|
|
17
18
|
/** Props accepted by the plaza section. */
|
|
18
19
|
export type SkillPlazaSectionProps = Partial<InjectFace<SkillPlazaSectionInjected>>;
|
|
20
|
+
/** One department option derived from the rows the plaza can show. */
|
|
21
|
+
export interface PlazaDepartmentOption {
|
|
22
|
+
/** Value the select submits: the department code when the row carries one. */
|
|
23
|
+
code: string;
|
|
24
|
+
/** Label shown in the select; never empty. */
|
|
25
|
+
name: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The department a plaza row belongs to.
|
|
29
|
+
*
|
|
30
|
+
* The backend normally sends `departmentCode`; a legacy row may carry only a name or path, so the
|
|
31
|
+
* fallbacks keep those rows selectable instead of dropping them into an empty key.
|
|
32
|
+
* @param skill - one plaza row.
|
|
33
|
+
* @returns the code, the name, or the deepest path name; empty when the row says nothing.
|
|
34
|
+
*/
|
|
35
|
+
export declare function departmentKeyOf(skill: PlazaSkill): string;
|
|
36
|
+
/**
|
|
37
|
+
* The department choices present in the visible rows.
|
|
38
|
+
*
|
|
39
|
+
* The label falls back from the row's own name to the deepest path name and finally to the code,
|
|
40
|
+
* so a department the backend can no longer name still renders as a readable option.
|
|
41
|
+
* @param skills - every row the current query returned.
|
|
42
|
+
* @returns distinct departments, sorted by label.
|
|
43
|
+
*/
|
|
44
|
+
export declare function departmentOptionsOf(skills: readonly PlazaSkill[]): PlazaDepartmentOption[];
|
|
45
|
+
/**
|
|
46
|
+
* The rows the department tab shows.
|
|
47
|
+
* @param skills - every row the current query returned.
|
|
48
|
+
* @param code - selected department key, or an empty string for all of them.
|
|
49
|
+
* @returns the department-shared rows, narrowed to `code` when it is set.
|
|
50
|
+
*/
|
|
51
|
+
export declare function filterByDepartment(skills: readonly PlazaSkill[], code: string): PlazaSkill[];
|
|
19
52
|
/**
|
|
20
53
|
* Browse, share, and install skills other users published.
|
|
21
54
|
*
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/** English managed DataOps Settings copy. */
|
|
2
2
|
export declare const en: {
|
|
3
|
-
readonly nav: "
|
|
4
|
-
readonly title: "
|
|
5
|
-
readonly managed: "Connected to
|
|
6
|
-
readonly unavailable: "
|
|
3
|
+
readonly nav: "万相AI中台";
|
|
4
|
+
readonly title: "万相AI中台";
|
|
5
|
+
readonly managed: "Connected to 万相AI中台";
|
|
6
|
+
readonly unavailable: "万相AI中台 is not connected.";
|
|
7
7
|
readonly loadFailed: "Loading failed. Try again.";
|
|
8
8
|
readonly brandingTitle: "Wanxiang branding";
|
|
9
9
|
readonly brandingToggle: "Wanxiang branding";
|
|
@@ -13,7 +13,7 @@ export declare const en: {
|
|
|
13
13
|
readonly limitsInvalid: "Enter a size in MiB.";
|
|
14
14
|
readonly limitsSave: "Save";
|
|
15
15
|
readonly limitsSaving: "Saving…";
|
|
16
|
-
readonly timeoutTitle: "
|
|
16
|
+
readonly timeoutTitle: "万相AI中台 query timeout";
|
|
17
17
|
readonly timeoutLabel: "Longest single query (seconds)";
|
|
18
18
|
readonly timeoutInvalid: "Enter 1 to 3600 seconds.";
|
|
19
19
|
readonly defaultsTitle: "Default configuration";
|
|
@@ -23,7 +23,6 @@ export declare const en: {
|
|
|
23
23
|
readonly defaultsNeverPublished: "Not published yet";
|
|
24
24
|
readonly defaultsNotAdmin: "Administrators only";
|
|
25
25
|
readonly plazaTitle: "Skill plaza";
|
|
26
|
-
readonly plazaAllTags: "All tags";
|
|
27
26
|
readonly plazaMine: "Shared by me";
|
|
28
27
|
readonly plazaShowDetail: "Details";
|
|
29
28
|
readonly plazaClose: "Close";
|
|
@@ -34,7 +33,7 @@ export declare const en: {
|
|
|
34
33
|
readonly plazaUnshare: "Stop sharing";
|
|
35
34
|
readonly plazaUnshared: "Removed from the plaza. Users who installed it keep their copy.";
|
|
36
35
|
readonly plazaEmpty: "No skills have been shared yet.";
|
|
37
|
-
readonly plazaUnavailable: "The skill plaza is available once this workspace is connected to
|
|
36
|
+
readonly plazaUnavailable: "The skill plaza is available once this workspace is connected to 万相AI中台.";
|
|
38
37
|
readonly plazaLoadFailed: "The skill plaza could not be loaded. Try again.";
|
|
39
38
|
readonly plazaActionFailed: "The action could not be completed. Try again.";
|
|
40
39
|
readonly plazaRetry: "Try again";
|
|
@@ -42,6 +41,10 @@ export declare const en: {
|
|
|
42
41
|
readonly plazaTabApproved: "Approved";
|
|
43
42
|
readonly plazaTabPending: "Pending";
|
|
44
43
|
readonly plazaTabAll: "All";
|
|
44
|
+
readonly plazaTabDepartment: "Department";
|
|
45
|
+
readonly plazaDepartment: "Department";
|
|
46
|
+
readonly plazaDepartmentAll: "All departments";
|
|
47
|
+
readonly plazaDepartmentEmpty: "No department-shared skills yet.";
|
|
45
48
|
readonly plazaSharedTag: "Shared";
|
|
46
49
|
readonly plazaReview: "Review";
|
|
47
50
|
readonly plazaPendingEmpty: "No skills are waiting for review.";
|
|
@@ -123,9 +126,9 @@ export declare const en: {
|
|
|
123
126
|
readonly permStatusApproved: "Approved";
|
|
124
127
|
readonly permStatusRejected: "Rejected";
|
|
125
128
|
readonly permDemo: "Demo data";
|
|
126
|
-
readonly reloginTitle: "Sign in to
|
|
127
|
-
readonly reloginBody: "
|
|
128
|
-
readonly reloginAction: "
|
|
129
|
+
readonly reloginTitle: "Sign in to 万相AI中台 again";
|
|
130
|
+
readonly reloginBody: "Please sign in again.";
|
|
131
|
+
readonly reloginAction: "Sign in again";
|
|
129
132
|
readonly reloginDismiss: "Later";
|
|
130
133
|
readonly upgradeAvailableTitle: "A new workspace version is available";
|
|
131
134
|
readonly upgradeAvailableBody: "Restart the workspace to apply it.";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The DataOps connector's workspace-visible state and skill-center catalogue.
|
|
3
|
+
*
|
|
4
|
+
* The skill center is skill-first, with connectors as its fourth tab. DataOps contributes one
|
|
5
|
+
* connector whose capability rows are derived from the same scope that drives tool disclosure;
|
|
6
|
+
* a capability the account cannot use is omitted rather than rendered as a disabled placeholder.
|
|
7
|
+
*
|
|
8
|
+
* This module owns only state and presentation. It never authorizes a tool call or a DataOps
|
|
9
|
+
* request: the backend remains the final boundary.
|
|
10
|
+
*
|
|
11
|
+
* @module connector-state
|
|
12
|
+
*/
|
|
13
|
+
import { type DataOpsScope } from './tool-scope.ts';
|
|
14
|
+
/** Same-origin route the skill center's connector tab reads and writes. */
|
|
15
|
+
export declare const CONNECTORS_PATH = "/api/dsh-skill-center/connectors";
|
|
16
|
+
/** Connector id shown in the skill center. */
|
|
17
|
+
export declare const DATAOPS_CONNECTOR_ID = "dataops-managed";
|
|
18
|
+
/** Persisted enable state; a missing file means the connector is off. */
|
|
19
|
+
export interface DataOpsConnectorState {
|
|
20
|
+
enabled: boolean;
|
|
21
|
+
capabilities: Record<string, boolean>;
|
|
22
|
+
}
|
|
23
|
+
/** One capability row the skill center renders. */
|
|
24
|
+
export interface ConnectorCapabilityView {
|
|
25
|
+
id: string;
|
|
26
|
+
name: string;
|
|
27
|
+
description: string;
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
permitted: boolean;
|
|
30
|
+
unavailableReason?: string;
|
|
31
|
+
}
|
|
32
|
+
/** One connector row the skill center renders. */
|
|
33
|
+
export interface ConnectorView {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
vendor: string;
|
|
37
|
+
description: string;
|
|
38
|
+
enabled: boolean;
|
|
39
|
+
connected: boolean;
|
|
40
|
+
capabilities: ConnectorCapabilityView[];
|
|
41
|
+
}
|
|
42
|
+
/** Resolve the persisted state file without depending on a global DSH path. */
|
|
43
|
+
export declare const CONNECTOR_STATE_PATH: string;
|
|
44
|
+
/** Read state; a missing or corrupt file is a safe disabled state. */
|
|
45
|
+
export declare function readConnectorState(path?: string): Promise<DataOpsConnectorState>;
|
|
46
|
+
/** Persist state atomically. */
|
|
47
|
+
export declare function writeConnectorState(state: DataOpsConnectorState, path?: string): Promise<void>;
|
|
48
|
+
/** Build the connector row for one current scope. */
|
|
49
|
+
export declare function buildConnectorView(scope: DataOpsScope | undefined, state: DataOpsConnectorState): ConnectorView;
|
|
50
|
+
/** Whether one capability id is in the fixed catalogue. */
|
|
51
|
+
export declare function hasCapability(id: string): boolean;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Owns the managed tool set for the workspace's current DataOps principal.
|
|
3
|
+
*
|
|
4
|
+
* A DSH workspace belongs to exactly one DataOps user, so the model's tool list can be derived
|
|
5
|
+
* from that user's current scope. The manager is the only writer of that list: it reads
|
|
6
|
+
* `/api/ai/review/context` when the credential changes (the `credentials/reference-updated`
|
|
7
|
+
* event, which is also what JWT adoption and `managed-auth` produce), on the first connect, and
|
|
8
|
+
* when the review-context route observes a different role set. It then reconciles the registry:
|
|
9
|
+
* newly allowed tools are registered, tools the role lost are disposed, and tools that remain
|
|
10
|
+
* allowed keep their registration.
|
|
11
|
+
*
|
|
12
|
+
* The manager never runs inside a tool call. Execution-time authorization belongs to DataOps;
|
|
13
|
+
* this is disclosure, so a scope sync that fails transiently keeps the current set rather than
|
|
14
|
+
* flapping, while a definitive 401/403/404/503 clears it.
|
|
15
|
+
*
|
|
16
|
+
* @module tool-manager
|
|
17
|
+
*/
|
|
18
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
19
|
+
import type { ReviewApi, ReviewApiResult } from './review-api.ts';
|
|
20
|
+
import { type AiToolName, type DataOpsScope } from './tool-scope.ts';
|
|
21
|
+
/**
|
|
22
|
+
* Reconcile the managed tools with the current DataOps scope.
|
|
23
|
+
*
|
|
24
|
+
* Construct once per plugin activation. `refresh` is idempotent and coalesces concurrent calls;
|
|
25
|
+
* the constructor also subscribes to the credential observation so an expired session removes
|
|
26
|
+
* the tools without waiting for another explicit sync.
|
|
27
|
+
*/
|
|
28
|
+
export declare class AiToolManager {
|
|
29
|
+
private readonly ctx;
|
|
30
|
+
private readonly reviewApi;
|
|
31
|
+
private readonly definitions;
|
|
32
|
+
private readonly registrations;
|
|
33
|
+
private readonly unsubscribeExpiry;
|
|
34
|
+
private disposeViewer;
|
|
35
|
+
private scope;
|
|
36
|
+
private revision;
|
|
37
|
+
private inflight;
|
|
38
|
+
private disposed;
|
|
39
|
+
constructor(ctx: Context, reviewApi: ReviewApi);
|
|
40
|
+
/** The scope the current registration set was derived from, when one has been read. */
|
|
41
|
+
currentScope(): DataOpsScope | undefined;
|
|
42
|
+
/** The tool names currently registered, for diagnostics and tests. */
|
|
43
|
+
registeredToolNames(): AiToolName[];
|
|
44
|
+
/**
|
|
45
|
+
* Re-read the workspace's DataOps scope and reconcile the tool list.
|
|
46
|
+
*
|
|
47
|
+
* Definitive denials clear the list; transient failures (network, 5xx) keep what the last good
|
|
48
|
+
* observation produced. The method never throws: sync happens in the background of a browser
|
|
49
|
+
* request and must not fail that request.
|
|
50
|
+
* @param force - Start a new read even when one is already in flight. Credential adoption uses
|
|
51
|
+
* this because an older read may have captured the previous token; its answer must not win
|
|
52
|
+
* over the scope the new token produces.
|
|
53
|
+
*/
|
|
54
|
+
refresh(force?: boolean): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* Apply a context answer already fetched by a same-origin route.
|
|
57
|
+
*
|
|
58
|
+
* The review-context route is how the UI learns about a role change, so its answer is also the
|
|
59
|
+
* freshest scope this plugin will see. Reusing it avoids a second fetch on a page that polls.
|
|
60
|
+
* @param result - The upstream status and parsed body.
|
|
61
|
+
*/
|
|
62
|
+
observeContext(result: ReviewApiResult): void;
|
|
63
|
+
/**
|
|
64
|
+
* Apply one parsed context body without contacting DataOps.
|
|
65
|
+
* @param body - Parsed `/api/ai/review/context` body.
|
|
66
|
+
*/
|
|
67
|
+
applyContext(body: unknown): void;
|
|
68
|
+
/** Drop every managed tool because no usable DataOps scope is known. */
|
|
69
|
+
clear(): void;
|
|
70
|
+
/** Dispose every tool this manager registered and stop observing auth state. */
|
|
71
|
+
dispose(): void;
|
|
72
|
+
private loadScope;
|
|
73
|
+
/** Store the scope and make the registry match it. */
|
|
74
|
+
private applyScope;
|
|
75
|
+
/** Map one DataOps scope to the skill registry's audience filter. */
|
|
76
|
+
private applyViewer;
|
|
77
|
+
/** Dispose one registration, tolerating a disposer that has already run. */
|
|
78
|
+
private disposeRegistration;
|
|
79
|
+
/** Dispose the whole current set. */
|
|
80
|
+
private disposeRegistrations;
|
|
81
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The canonical output contract shared by this plugin's model tools.
|
|
3
|
+
*
|
|
4
|
+
* Every managed tool returns one JSON value and renders it as pretty-printed text. Keeping the
|
|
5
|
+
* schema and renderer in one place means a new tool cannot accidentally return a shape the tool
|
|
6
|
+
* registry rejects.
|
|
7
|
+
*
|
|
8
|
+
* @module tool-output
|
|
9
|
+
*/
|
|
10
|
+
/** JSON-safe content a tool may return. */
|
|
11
|
+
export type Json = null | boolean | number | string | Json[] | {
|
|
12
|
+
[key: string]: Json;
|
|
13
|
+
};
|
|
14
|
+
/** One text block as the tool registry accepts it. */
|
|
15
|
+
export type ToolContent = {
|
|
16
|
+
type: 'text';
|
|
17
|
+
text: string;
|
|
18
|
+
};
|
|
19
|
+
/** An open object schema: tools declare their canonical shape in code and docs. */
|
|
20
|
+
export declare const OPEN_OBJECT_SCHEMA: {
|
|
21
|
+
readonly type: "object";
|
|
22
|
+
readonly additionalProperties: true;
|
|
23
|
+
};
|
|
24
|
+
/** Render one canonical value as pretty JSON text. */
|
|
25
|
+
export declare const renderJson: (_args: unknown, value: unknown) => ToolContent[];
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which DataOps tools this workspace's model may see.
|
|
3
|
+
*
|
|
4
|
+
* This module is disclosure, not authorization. DataOps remains the only authority for every
|
|
5
|
+
* API call: its AuthGuard and PermissionGuard resolve the caller's current roles and grants,
|
|
6
|
+
* and every managed tool call still crosses that boundary. What happens here is narrower — a
|
|
7
|
+
* DSH workspace belongs to one DataOps principal, so the tool schemas offered to the model can
|
|
8
|
+
* follow that principal's current scope instead of advertising operations the role cannot use.
|
|
9
|
+
*
|
|
10
|
+
* The scope is the answer `/api/ai/review/context` gives for the stored JWT. It is read when the
|
|
11
|
+
* credential changes, on the first connect, and when the review-context route observes a new
|
|
12
|
+
* role set — never inside a tool `execute`. A tool that is already registered and whose role
|
|
13
|
+
* loses the capability is disposed by the manager.
|
|
14
|
+
*
|
|
15
|
+
* @module tool-scope
|
|
16
|
+
*/
|
|
17
|
+
/** The subset of `/api/ai/review/context` that tool disclosure needs. */
|
|
18
|
+
export interface DataOpsScope {
|
|
19
|
+
/** Stable DataOps user id. */
|
|
20
|
+
userId?: string;
|
|
21
|
+
/** Login name, used only for diagnostics. */
|
|
22
|
+
username?: string;
|
|
23
|
+
/** Role display codes such as 超级管理员/管理员/部门管理员/应用人员. */
|
|
24
|
+
roleCodes?: string[];
|
|
25
|
+
/** The primary role code; DataOps normalizes it to one of the four portal roles. */
|
|
26
|
+
primaryRole?: string;
|
|
27
|
+
/** Stable role keys such as SUPER_ADMIN, when the context carries them. */
|
|
28
|
+
systemKeys?: string[];
|
|
29
|
+
/** The caller's department, when one is assigned. */
|
|
30
|
+
departmentCode?: string | null;
|
|
31
|
+
/**
|
|
32
|
+
* Effective permission keys from every role the caller holds.
|
|
33
|
+
*
|
|
34
|
+
* `permissions` is the current backend field; `permissionKeys` is accepted while a deployment
|
|
35
|
+
* still serves the older shape. A present but empty array is a denial, not a reason to fall
|
|
36
|
+
* back to role names.
|
|
37
|
+
*/
|
|
38
|
+
permissions?: string[];
|
|
39
|
+
/** Older spelling of {@link permissions}. */
|
|
40
|
+
permissionKeys?: string[];
|
|
41
|
+
/** Data scope DataOps resolved for the caller. */
|
|
42
|
+
dataScope?: {
|
|
43
|
+
all?: boolean;
|
|
44
|
+
self?: boolean;
|
|
45
|
+
departmentCodes?: string[];
|
|
46
|
+
};
|
|
47
|
+
/** Review capability as the current context reports it (older nested shape). */
|
|
48
|
+
review?: {
|
|
49
|
+
canReview?: boolean;
|
|
50
|
+
canReviewGlobal?: boolean;
|
|
51
|
+
canManagePermissions?: boolean;
|
|
52
|
+
};
|
|
53
|
+
/** Review capability as the current context reports it (top-level shape). */
|
|
54
|
+
canReview?: boolean;
|
|
55
|
+
/** Whether the caller may review global (department-less) resources. */
|
|
56
|
+
canReviewGlobal?: boolean;
|
|
57
|
+
}
|
|
58
|
+
/** The permission each managed tool needs before it is offered to the model. */
|
|
59
|
+
export interface ToolAccessPolicy {
|
|
60
|
+
/** One `skill:*` capability; checked against the context's effective permission list. */
|
|
61
|
+
readonly permission?: string;
|
|
62
|
+
/** Review capability from the context, independent of a permission key. */
|
|
63
|
+
readonly reviewer?: boolean;
|
|
64
|
+
/** Only the DataOps 超级管理员 role sees this tool. */
|
|
65
|
+
readonly superAdminOnly?: boolean;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Tool-to-capability disclosure map.
|
|
69
|
+
*
|
|
70
|
+
* `review_notifications_list/read` intentionally carry no policy: every authenticated principal
|
|
71
|
+
* owns their own notification inbox. `dataops_api_docs` is gated by the role itself because the
|
|
72
|
+
* backend grants the OpenAPI reader to 超级管理员 by default; an administrator who happens to hold
|
|
73
|
+
* `developer:openapi:read` is still outside that role and does not receive the tool.
|
|
74
|
+
*/
|
|
75
|
+
export declare const AI_TOOL_ACCESS: {
|
|
76
|
+
readonly skill_list: {
|
|
77
|
+
readonly permission: "skill:read";
|
|
78
|
+
};
|
|
79
|
+
readonly skill_get: {
|
|
80
|
+
readonly permission: "skill:read";
|
|
81
|
+
};
|
|
82
|
+
readonly skill_submit: {
|
|
83
|
+
readonly permission: "skill:submit";
|
|
84
|
+
};
|
|
85
|
+
readonly skill_install: {
|
|
86
|
+
readonly permission: "skill:install";
|
|
87
|
+
};
|
|
88
|
+
readonly skill_uninstall: {
|
|
89
|
+
readonly permission: "skill:install";
|
|
90
|
+
};
|
|
91
|
+
readonly skill_enable: {
|
|
92
|
+
readonly permission: "skill:manage";
|
|
93
|
+
};
|
|
94
|
+
readonly skill_disable: {
|
|
95
|
+
readonly permission: "skill:manage";
|
|
96
|
+
};
|
|
97
|
+
readonly skill_unshare: {
|
|
98
|
+
readonly permission: "skill:manage";
|
|
99
|
+
};
|
|
100
|
+
readonly skill_delete: {
|
|
101
|
+
readonly permission: "skill:manage";
|
|
102
|
+
};
|
|
103
|
+
readonly review_scan: {
|
|
104
|
+
readonly permission: "skill:read";
|
|
105
|
+
readonly reviewer: true;
|
|
106
|
+
};
|
|
107
|
+
readonly review_decide: {
|
|
108
|
+
readonly permission: "skill:review:decide";
|
|
109
|
+
readonly reviewer: true;
|
|
110
|
+
};
|
|
111
|
+
readonly review_notifications_list: {};
|
|
112
|
+
readonly review_notifications_read: {};
|
|
113
|
+
readonly dataops_api_docs: {
|
|
114
|
+
readonly superAdminOnly: true;
|
|
115
|
+
};
|
|
116
|
+
};
|
|
117
|
+
/** Every managed tool name, in the order the manager considers them. */
|
|
118
|
+
export declare const AI_TOOL_NAMES: AiToolName[];
|
|
119
|
+
/** One managed tool name. */
|
|
120
|
+
export type AiToolName = keyof typeof AI_TOOL_ACCESS;
|
|
121
|
+
/** Whether the context carries the stable super-administrator role. */
|
|
122
|
+
export declare const isSuperAdmin: (scope: DataOpsScope) => boolean;
|
|
123
|
+
/** Whether the context carries an administrator or super-administrator role. */
|
|
124
|
+
export declare const isAdmin: (scope: DataOpsScope) => boolean;
|
|
125
|
+
/** Whether the context carries a department-administrator role. */
|
|
126
|
+
export declare const isDepartmentAdmin: (scope: DataOpsScope) => boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Whether the caller may run review operations.
|
|
129
|
+
*
|
|
130
|
+
* The current backend returns `canReview` at the top level; the nested `review` shape is
|
|
131
|
+
* accepted for older contexts. Role membership is a fallback, not an override.
|
|
132
|
+
*/
|
|
133
|
+
export declare const canReview: (scope: DataOpsScope) => boolean;
|
|
134
|
+
/**
|
|
135
|
+
* Whether the caller holds one capability.
|
|
136
|
+
*
|
|
137
|
+
* A context that carries a permission list is authoritative. Before the list existed, the
|
|
138
|
+
* four-role fallback kept the old behavior; it is only used when neither spelling is present.
|
|
139
|
+
*/
|
|
140
|
+
export declare const hasPermission: (scope: DataOpsScope, key: string) => boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Whether one tool should be registered for this scope.
|
|
143
|
+
*
|
|
144
|
+
* Every check is disclosure metadata, not an execution gate: a visible tool still lets DataOps
|
|
145
|
+
* answer with 403 when the underlying grant changed between a scope sync and the call.
|
|
146
|
+
*/
|
|
147
|
+
export declare function isToolVisible(name: AiToolName, scope: DataOpsScope): boolean;
|
|
148
|
+
/**
|
|
149
|
+
* Validate one `/api/ai/review/context` body.
|
|
150
|
+
*
|
|
151
|
+
* A body without `roleCodes` is not a usable scope: treating it as "no roles" would let the
|
|
152
|
+
* role-name fallback hand an ordinary capability set to an answer this plugin cannot interpret.
|
|
153
|
+
* @param body - Parsed JSON from the context endpoint.
|
|
154
|
+
* @returns The normalized scope, or undefined when the answer is not a context document.
|
|
155
|
+
*/
|
|
156
|
+
export declare function normalizeScope(body: unknown): DataOpsScope | undefined;
|
package/lib/types/tools.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Model tools for the skill centre
|
|
2
|
+
* Model tools for the skill centre, the shared review queues, and the DataOps API reference.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Registration is disclosure, not authorization. `AiToolManager` computes the workspace's
|
|
5
|
+
* current DataOps scope once per credential/role sync and registers only the tools that scope
|
|
6
|
+
* can use; these definitions assume they were registered for a principal that may call them.
|
|
7
|
+
* Every `execute` therefore validates parameters, asks for user approval where a mutation needs
|
|
8
|
+
* it, writes its audit line, and lets DataOps answer — no per-call role read, no local permission
|
|
9
|
+
* tree, and no replacement for the backend's AuthGuard/PermissionGuard.
|
|
8
10
|
*
|
|
9
11
|
* The skill plaza is the catalogue. `scope=mine` exposes the caller's pending/rejected rows,
|
|
10
12
|
* the public list exposes what the caller may install, and the admin list (administrators only)
|
|
@@ -16,6 +18,24 @@
|
|
|
16
18
|
* @module tools
|
|
17
19
|
*/
|
|
18
20
|
import type { Context } from '@deepseek-ai/cordis';
|
|
21
|
+
import { type ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
19
22
|
import type { ReviewApi } from './review-api.ts';
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
import { type AiToolName, type DataOpsScope } from './tool-scope.ts';
|
|
24
|
+
/** One managed tool together with the name the disclosure map uses. */
|
|
25
|
+
export interface AiManagedTool {
|
|
26
|
+
name: AiToolName;
|
|
27
|
+
definition: ToolDefinition;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build every managed tool definition.
|
|
31
|
+
*
|
|
32
|
+
* The definitions are built once; `readScope` is a live getter so a tool that survives a role
|
|
33
|
+
* change reports the scope the manager currently holds instead of the one it was registered
|
|
34
|
+
* with. An undefined scope can only be observed by an in-flight call during disposal, where the
|
|
35
|
+
* safe behavior is to show only the public catalogue.
|
|
36
|
+
* @param ctx - Host context used for local skills, approval and audit.
|
|
37
|
+
* @param reviewApi - Same-origin bridge holding the workspace JWT.
|
|
38
|
+
* @param readScope - Live accessor for the manager's current scope.
|
|
39
|
+
* @returns One definition per managed tool name.
|
|
40
|
+
*/
|
|
41
|
+
export declare function buildAiManagementTools(ctx: Context, reviewApi: ReviewApi, readScope: () => DataOpsScope | undefined): AiManagedTool[];
|
package/package.json
CHANGED