@chengxianglibra/dsh-data-analysis 0.1.1 → 0.1.2
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 +83 -34
- package/lib/bin/environment.js +1 -1
- package/lib/bin/presentation-lint.js +21 -0
- package/lib/bridges.js +2 -2
- package/lib/client/credentials/create-datasource.js +67 -0
- package/lib/client/credentials/install.js +191 -0
- package/lib/client/credentials/model.js +494 -0
- package/lib/client/credentials/styles.js +17 -0
- package/lib/client/workspace-header-action.js +11 -0
- package/lib/client.js +1133 -736
- package/lib/compatibility.js +1 -1
- package/lib/datasource/authoring.js +70 -0
- package/lib/datasource/bridge-programs.js +30 -18
- package/lib/datasource/bridge.js +100 -8
- package/lib/datasource/index.js +3 -2
- package/lib/datasource/python.js +115 -0
- package/lib/datasource/resolver-program.js +112 -0
- package/lib/datasource/rpc.js +88 -0
- package/lib/datasource/service.js +687 -0
- package/lib/datasource/shell-env.js +2 -333
- package/lib/datasource/test.js +24 -51
- package/lib/environment/binding.js +54 -9
- package/lib/environment/runtime.js +89 -78
- package/lib/environment/subprocess.js +6 -1
- package/lib/environment/summary.js +3 -3
- package/lib/environment/workspace.js +5 -0
- package/lib/index.js +1 -1
- package/lib/plugin.js +168 -51
- package/lib/presentation/assets/portable.js +606 -0
- package/lib/presentation/assets/static.js +945 -0
- package/lib/presentation/build/index.js +71 -0
- package/lib/presentation/commit.js +156 -0
- package/lib/presentation/contracts/charts.js +476 -0
- package/lib/presentation/contracts/editing.js +58 -0
- package/lib/presentation/contracts/index.js +651 -0
- package/lib/presentation/contracts/interaction.js +62 -0
- package/lib/presentation/contracts/types.js +15 -0
- package/lib/presentation/contracts/values.js +28 -0
- package/lib/presentation/delivery.js +56 -0
- package/lib/presentation/files.js +83 -0
- package/lib/presentation/index.js +6 -0
- package/lib/presentation/lint.js +46 -0
- package/lib/presentation/projection/files.js +85 -0
- package/lib/presentation/projection/index.js +195 -0
- package/lib/presentation/projection/program.js +144 -0
- package/lib/presentation/projection/sql-code.js +79 -0
- package/lib/presentation/receipt.js +34 -0
- package/lib/presentation/reports.js +122 -0
- package/lib/presentation/rpc.js +148 -0
- package/lib/presentation/tool.js +122 -0
- package/lib/python-execution-contracts.js +1 -0
- package/lib/python-execution.js +232 -0
- package/lib/semantic-browser/contracts.js +80 -0
- package/lib/semantic-browser/definition.js +64 -0
- package/lib/semantic-browser/program.js +119 -0
- package/lib/semantic-browser/service.js +72 -0
- package/lib/semantic-reference/bridge.js +118 -0
- package/lib/semantic-reference/contracts.js +113 -0
- package/lib/semantic-reference/index.js +3 -0
- package/lib/semantic-reference/rpc.js +122 -0
- package/lib/semantic-reference/search.js +94 -0
- package/lib/semantic-reference/usage.js +143 -0
- package/lib/types/bridges.d.ts +2 -2
- package/lib/types/client/credentials/create-datasource.d.ts +5 -0
- package/lib/types/client/credentials/install.d.ts +1 -0
- package/lib/types/client/credentials/model.d.ts +59 -0
- package/lib/types/client/credentials/styles.d.ts +1 -0
- package/lib/types/client/presentation/ask-dsh.d.ts +9 -0
- package/lib/types/client/presentation/chart-explorer.d.ts +12 -0
- package/lib/types/client/presentation/chart-geometry.d.ts +30 -0
- package/lib/types/client/presentation/chart-renderer.d.ts +10 -0
- package/lib/types/client/presentation/chart-specials.d.ts +10 -0
- package/lib/types/client/presentation/chart-styles.d.ts +1 -0
- package/lib/types/client/presentation/chart-tooltip.d.ts +13 -0
- package/lib/types/client/presentation/chart-view.d.ts +23 -0
- package/lib/types/client/presentation/copy-context.d.ts +5 -0
- package/lib/types/client/presentation/delivery-model.d.ts +54 -0
- package/lib/types/client/presentation/delivery.d.ts +77 -0
- package/lib/types/client/presentation/editor-controls.d.ts +12 -0
- package/lib/types/client/presentation/global-filters.d.ts +6 -0
- package/lib/types/client/presentation/host-entry.d.ts +12 -0
- package/lib/types/client/presentation/icons.d.ts +4 -0
- package/lib/types/client/presentation/install.d.ts +15 -0
- package/lib/types/client/presentation/markdown.d.ts +5 -0
- package/lib/types/client/presentation/model.d.ts +62 -0
- package/lib/types/client/presentation/reader.d.ts +11 -0
- package/lib/types/client/presentation/source-code-model.d.ts +16 -0
- package/lib/types/client/presentation/source-code.d.ts +11 -0
- package/lib/types/client/presentation/source-dialog.d.ts +13 -0
- package/lib/types/client/presentation/source-facts.d.ts +20 -0
- package/lib/types/client/presentation/source-format.d.ts +5 -0
- package/lib/types/client/presentation/source-highlight.d.ts +7 -0
- package/lib/types/client/presentation/sources.d.ts +13 -0
- package/lib/types/client/presentation/styles.d.ts +1 -0
- package/lib/types/client/presentation/table.d.ts +12 -0
- package/lib/types/client/presentation/use-element-width.d.ts +5 -0
- package/lib/types/client/workspace-header-action.d.ts +7 -0
- package/lib/types/client.d.ts +4 -122
- package/lib/types/compatibility.d.ts +2 -2
- package/lib/types/datasource/authoring.d.ts +25 -0
- package/lib/types/datasource/bridge.d.ts +21 -3
- package/lib/types/datasource/index.d.ts +3 -5
- package/lib/types/datasource/python.d.ts +4 -0
- package/lib/types/datasource/resolver-program.d.ts +6 -0
- package/lib/types/datasource/rpc.d.ts +4 -0
- package/lib/types/datasource/service.d.ts +112 -0
- package/lib/types/datasource/shell-env.d.ts +0 -29
- package/lib/types/datasource/test.d.ts +6 -29
- package/lib/types/environment/binding.d.ts +5 -1
- package/lib/types/environment/errors.d.ts +1 -1
- package/lib/types/environment/runtime.d.ts +5 -5
- package/lib/types/environment/types.d.ts +13 -2
- package/lib/types/index.d.ts +1 -1
- package/lib/types/plugin.d.ts +11 -5
- package/lib/types/presentation/build/index.d.ts +11 -0
- package/lib/types/presentation/commit.d.ts +4 -0
- package/lib/types/presentation/contracts/charts.d.ts +13 -0
- package/lib/types/presentation/contracts/editing.d.ts +8 -0
- package/lib/types/presentation/contracts/index.d.ts +11 -0
- package/lib/types/presentation/contracts/interaction.d.ts +8 -0
- package/lib/types/presentation/contracts/types.d.ts +252 -0
- package/lib/types/presentation/contracts/values.d.ts +9 -0
- package/lib/types/presentation/delivery.d.ts +3 -0
- package/lib/types/presentation/files.d.ts +7 -0
- package/lib/types/presentation/index.d.ts +6 -0
- package/lib/types/presentation/lint.d.ts +13 -0
- package/lib/types/presentation/projection/files.d.ts +2 -0
- package/lib/types/presentation/projection/index.d.ts +20 -0
- package/lib/types/presentation/projection/program.d.ts +2 -0
- package/lib/types/presentation/projection/sql-code.d.ts +2 -0
- package/lib/types/presentation/receipt.d.ts +14 -0
- package/lib/types/presentation/reports.d.ts +5 -0
- package/lib/types/presentation/rpc.d.ts +24 -0
- package/lib/types/presentation/tool.d.ts +12 -0
- package/lib/types/python-execution-contracts.d.ts +10 -0
- package/lib/types/python-execution.d.ts +10 -0
- package/lib/types/semantic-browser/contracts.d.ts +40 -0
- package/lib/types/semantic-browser/definition.d.ts +20 -0
- package/lib/types/semantic-browser/program.d.ts +2 -0
- package/lib/types/semantic-browser/service.d.ts +20 -0
- package/lib/types/semantic-reference/bridge.d.ts +11 -0
- package/lib/types/semantic-reference/contracts.d.ts +51 -0
- package/lib/types/semantic-reference/index.d.ts +3 -0
- package/lib/types/semantic-reference/rpc.d.ts +15 -0
- package/lib/types/semantic-reference/search.d.ts +10 -0
- package/lib/types/semantic-reference/usage.d.ts +43 -0
- package/package.json +43 -19
- package/python/presentation-kit/dist/dsh_data_analysis_presentation_kit-1.1.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-presentation/SKILL.md +24 -0
- package/skills/dsh-data-analysis-presentation/references/chart-examples.md +18 -0
- package/skills/dsh-data-analysis-presentation/references/charts.md +55 -0
- package/skills/dsh-data-analysis-presentation/references/examples/artifact.draft.json +27 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-bins.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-boxes.dataset.json +45 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-composition.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-funnel.dataset.json +27 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-precision.dataset.json +21 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-series.dataset.json +58 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-shares.dataset.json +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts-waterfall.dataset.json +44 -0
- package/skills/dsh-data-analysis-presentation/references/examples/charts.draft.json +392 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison-selected.dataset.json +24 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison-summary.dataset.json +22 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.dataset.json +18 -0
- package/skills/dsh-data-analysis-presentation/references/examples/comparison.draft.json +79 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed-sources.draft.json +35 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed.dataset.json +12 -0
- package/skills/dsh-data-analysis-presentation/references/examples/computed.draft.json +57 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction-detail.dataset.json +30 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction-summary.dataset.json +22 -0
- package/skills/dsh-data-analysis-presentation/references/examples/interaction.draft.json +278 -0
- package/skills/dsh-data-analysis-presentation/references/examples/source-only.draft.json +19 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-charts.py +73 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-comparison.py +42 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-computed.py +21 -0
- package/skills/dsh-data-analysis-presentation/references/examples/write-interaction.py +33 -0
- package/skills/dsh-data-analysis-presentation/references/examples.md +42 -0
- package/skills/dsh-data-analysis-presentation/references/interaction.md +56 -0
- package/skills/dsh-data-analysis-presentation/references/narrative.md +37 -0
- package/skills/dsh-data-analysis-presentation/references/schema.md +138 -0
- package/lib/datasource/access.js +0 -60
- package/lib/datasource/credentials.js +0 -24
- package/lib/evidence/bridge-program.js +0 -153
- package/lib/evidence/bridge.js +0 -166
- package/lib/evidence/index.js +0 -2
- package/lib/evidence/sources.js +0 -263
- package/lib/types/datasource/access.d.ts +0 -27
- package/lib/types/evidence/bridge.d.ts +0 -40
- package/lib/types/evidence/index.d.ts +0 -4
- package/lib/types/evidence/sources.d.ts +0 -57
- package/python/report-kit/dist/dsh_data_analysis_report_kit-3.0.0-py3-none-any.whl +0 -0
- package/skills/dsh-data-analysis-report/SKILL.md +0 -57
- package/skills/dsh-data-analysis-report/assets/marivo-artifact.js +0 -113
- package/skills/dsh-data-analysis-report/assets/marivo-session-dag.js +0 -1201
- package/skills/dsh-data-analysis-report/assets/report-data.js +0 -449
package/lib/compatibility.js
CHANGED
|
@@ -46,7 +46,7 @@ function parsePackageManifest(value) {
|
|
|
46
46
|
packageSpec: requiredString(marivo.packageSpec, 'package compatibility marivo.packageSpec'),
|
|
47
47
|
}),
|
|
48
48
|
contracts: Object.freeze({
|
|
49
|
-
runtimeInstallation: requiredLiteral(contracts.runtimeInstallation, 'dsh-data-analysis-runtime/
|
|
49
|
+
runtimeInstallation: requiredLiteral(contracts.runtimeInstallation, 'dsh-data-analysis-runtime/v3', 'package compatibility contracts.runtimeInstallation'),
|
|
50
50
|
subprocessPolicy: requiredLiteral(contracts.subprocessPolicy, 'direct-argv-inherited-env-snapshot-overlay-v2', 'package compatibility contracts.subprocessPolicy'),
|
|
51
51
|
}),
|
|
52
52
|
}),
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const datasourceAuthoringSchema = z.object({
|
|
3
|
+
fingerprint: z.string(),
|
|
4
|
+
backends: z.array(z.object({
|
|
5
|
+
name: z.string(),
|
|
6
|
+
fields: z.array(z.object({
|
|
7
|
+
name: z.string(),
|
|
8
|
+
type: z.enum(['string', 'number', 'boolean', 'json']),
|
|
9
|
+
required: z.boolean(),
|
|
10
|
+
description: z.string(),
|
|
11
|
+
default: z.unknown().optional(),
|
|
12
|
+
})),
|
|
13
|
+
})),
|
|
14
|
+
});
|
|
15
|
+
// The public DatasourceSpec union and dataclass fields are read from the checked Runtime.
|
|
16
|
+
// No backend registry or field schema is duplicated in the plugin.
|
|
17
|
+
export const DATASOURCE_AUTHORING_PROGRAM = String.raw `
|
|
18
|
+
import contextlib
|
|
19
|
+
import dataclasses
|
|
20
|
+
import json
|
|
21
|
+
import sys
|
|
22
|
+
import typing
|
|
23
|
+
import marivo.datasource as md
|
|
24
|
+
|
|
25
|
+
class Discard:
|
|
26
|
+
def write(self, value): return len(value)
|
|
27
|
+
def flush(self): pass
|
|
28
|
+
|
|
29
|
+
def spec_fields(cls):
|
|
30
|
+
hints = typing.get_type_hints(cls)
|
|
31
|
+
result = []
|
|
32
|
+
for field in dataclasses.fields(cls):
|
|
33
|
+
if not field.init or field.name == "ai_context":
|
|
34
|
+
continue
|
|
35
|
+
annotation = hints[field.name]
|
|
36
|
+
types = set(typing.get_args(annotation) or [annotation]) - {type(None)}
|
|
37
|
+
kind = "string" if types == {str} else "boolean" if types == {bool} else "number" if types <= {int, float} else "json"
|
|
38
|
+
item = {"name": field.name, "type": kind,
|
|
39
|
+
"required": field.default is dataclasses.MISSING and field.default_factory is dataclasses.MISSING,
|
|
40
|
+
"description": field.metadata.get("description", "")}
|
|
41
|
+
if field.default is not dataclasses.MISSING:
|
|
42
|
+
item["default"] = field.default
|
|
43
|
+
result.append(item)
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
payload = json.load(sys.stdin)
|
|
48
|
+
with contextlib.redirect_stdout(Discard()), contextlib.redirect_stderr(Discard()):
|
|
49
|
+
specs = {cls.backend_type: cls for cls in typing.get_args(md.DatasourceSpec)}
|
|
50
|
+
if payload["action"] == "schema":
|
|
51
|
+
result = {"backends": [{"name": key, "fields": spec_fields(cls)} for key, cls in specs.items()]}
|
|
52
|
+
else:
|
|
53
|
+
cls = specs[payload["backend"]]
|
|
54
|
+
fields = payload["fields"]
|
|
55
|
+
allowed = {field["name"] for field in spec_fields(cls)}
|
|
56
|
+
if set(fields) - allowed:
|
|
57
|
+
raise ValueError("unknown-field")
|
|
58
|
+
spec = cls(**fields)
|
|
59
|
+
if any(item.name == spec.name for item in md.list()):
|
|
60
|
+
result = {"error": "datasource-already-exists"}
|
|
61
|
+
else:
|
|
62
|
+
# Validate references before writing; the Host applies its own reserved-name check too.
|
|
63
|
+
if sorted(spec.env_refs.values()) != sorted(payload["refs"]):
|
|
64
|
+
raise ValueError("credential-reference-mismatch")
|
|
65
|
+
md.register(spec)
|
|
66
|
+
result = {"name": spec.name}
|
|
67
|
+
except Exception:
|
|
68
|
+
result = {"error": "datasource-definition-invalid"}
|
|
69
|
+
print(json.dumps(result))
|
|
70
|
+
`.trim();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { RESOLVER_PROGRAM } from "./resolver-program.js";
|
|
1
2
|
export const MARIVO_DATASOURCE_DESCRIBE_PROGRAM = String.raw `
|
|
2
3
|
import json
|
|
3
4
|
import os
|
|
@@ -5,11 +6,17 @@ import sys
|
|
|
5
6
|
import marivo
|
|
6
7
|
import marivo.datasource as md
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
|
|
9
|
+
import hashlib
|
|
10
|
+
|
|
11
|
+
def project_description(description):
|
|
12
|
+
payload = {"name": description.name, "backend": description.backend_type,
|
|
13
|
+
"fields": description.literal_fields, "refs": description.env_refs}
|
|
14
|
+
return {"name": description.name, "backend": description.backend_type,
|
|
15
|
+
"properties": description.literal_fields, "refs": list(description.env_refs.values()),
|
|
16
|
+
"fields": description.env_refs,
|
|
17
|
+
"definition": hashlib.sha256(json.dumps(payload, sort_keys=True, default=str).encode()).hexdigest()}
|
|
18
|
+
|
|
19
|
+
print(json.dumps(project_description(md.describe(sys.argv[1])), sort_keys=True))
|
|
13
20
|
`.trim();
|
|
14
21
|
export const MARIVO_DATASOURCE_INVENTORY_PROGRAM = String.raw `
|
|
15
22
|
import json
|
|
@@ -18,14 +25,21 @@ import sys
|
|
|
18
25
|
import marivo
|
|
19
26
|
import marivo.datasource as md
|
|
20
27
|
|
|
28
|
+
import hashlib
|
|
29
|
+
|
|
30
|
+
def project_description(description):
|
|
31
|
+
payload = {"name": description.name, "backend": description.backend_type,
|
|
32
|
+
"fields": description.literal_fields, "refs": description.env_refs}
|
|
33
|
+
return {"name": description.name, "backend": description.backend_type,
|
|
34
|
+
"properties": description.literal_fields, "refs": list(description.env_refs.values()),
|
|
35
|
+
"fields": description.env_refs,
|
|
36
|
+
"definition": hashlib.sha256(json.dumps(payload, sort_keys=True, default=str).encode()).hexdigest()}
|
|
37
|
+
|
|
21
38
|
print(json.dumps({
|
|
22
|
-
"datasources": [
|
|
23
|
-
"name": description.name,
|
|
24
|
-
"refs": list(description.env_refs.values()),
|
|
25
|
-
} for description in (md.describe(item.name) for item in md.list())],
|
|
39
|
+
"datasources": [project_description(md.describe(item.name)) for item in md.list()],
|
|
26
40
|
}, sort_keys=True))
|
|
27
41
|
`.trim();
|
|
28
|
-
export const MARIVO_DATASOURCE_TEST_PROGRAM = String.raw `
|
|
42
|
+
export const MARIVO_DATASOURCE_TEST_PROGRAM = `${RESOLVER_PROGRAM}\n${String.raw `
|
|
29
43
|
import contextlib
|
|
30
44
|
import io
|
|
31
45
|
import json
|
|
@@ -34,17 +48,15 @@ import sys
|
|
|
34
48
|
import marivo
|
|
35
49
|
import marivo.datasource as md
|
|
36
50
|
|
|
51
|
+
payload = json.load(sys.stdin)
|
|
52
|
+
resolver = SnapshotResolver(payload)
|
|
53
|
+
secret_values = list(payload["values"].values())
|
|
37
54
|
captured_stdout = io.StringIO()
|
|
38
55
|
captured_stderr = io.StringIO()
|
|
39
56
|
try:
|
|
40
57
|
with contextlib.redirect_stdout(captured_stdout), contextlib.redirect_stderr(captured_stderr):
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
value
|
|
44
|
-
for ref in description.env_refs.values()
|
|
45
|
-
if (value := os.environ.get(ref))
|
|
46
|
-
]
|
|
47
|
-
result = md.test(sys.argv[1])
|
|
58
|
+
with md.credential_scope(resolver=resolver):
|
|
59
|
+
result = md.test(sys.argv[1])
|
|
48
60
|
except Exception as exc:
|
|
49
61
|
print(json.dumps({"kind": "datasource-test-failed", "exception_type": type(exc).__name__}), file=sys.stderr)
|
|
50
62
|
raise SystemExit(70)
|
|
@@ -77,4 +89,4 @@ print(json.dumps(redact({
|
|
|
77
89
|
"failure": failure,
|
|
78
90
|
"repair": repair,
|
|
79
91
|
}), sort_keys=True))
|
|
80
|
-
`.trim()
|
|
92
|
+
`.trim()}`;
|
package/lib/datasource/bridge.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { isCredentialRefName } from '@deepseek-ai/dsh-credentials';
|
|
2
2
|
import { MarivoEnvironmentError } from "../environment/errors.js";
|
|
3
3
|
import { resolveMarivoBridgeSource } from "../environment/source.js";
|
|
4
|
+
import { DATASOURCE_AUTHORING_PROGRAM, datasourceAuthoringSchema, } from "./authoring.js";
|
|
4
5
|
import { MARIVO_DATASOURCE_DESCRIBE_PROGRAM, MARIVO_DATASOURCE_INVENTORY_PROGRAM, MARIVO_DATASOURCE_TEST_PROGRAM, } from "./bridge-programs.js";
|
|
6
|
+
import { marivoCredentialStorageRef } from "./shell-env.js";
|
|
5
7
|
const DATASOURCE_LIMITS = Object.freeze({
|
|
6
8
|
timeoutMs: 30_000,
|
|
7
9
|
stdoutMaxBytes: 262_144,
|
|
@@ -24,6 +26,16 @@ function string(value) {
|
|
|
24
26
|
function nullableString(value) {
|
|
25
27
|
return value === null || typeof value === 'string';
|
|
26
28
|
}
|
|
29
|
+
function jsonValue(value) {
|
|
30
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean')
|
|
31
|
+
return true;
|
|
32
|
+
if (typeof value === 'number')
|
|
33
|
+
return Number.isFinite(value);
|
|
34
|
+
if (Array.isArray(value))
|
|
35
|
+
return value.every(jsonValue);
|
|
36
|
+
const fields = object(value);
|
|
37
|
+
return fields !== undefined && Object.values(fields).every(jsonValue);
|
|
38
|
+
}
|
|
27
39
|
function parseJsonObject(stdout, phase) {
|
|
28
40
|
try {
|
|
29
41
|
const parsed = JSON.parse(stdout.toString('utf8'));
|
|
@@ -41,15 +53,24 @@ function parseDescription(value, phase) {
|
|
|
41
53
|
if (source === undefined) {
|
|
42
54
|
throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource ${phase} returned invalid fields`, { phase });
|
|
43
55
|
}
|
|
44
|
-
exactKeys(source, ['name', 'refs'], phase);
|
|
56
|
+
exactKeys(source, ['name', 'backend', 'properties', 'refs', 'fields', 'definition'], phase);
|
|
45
57
|
const name = string(source.name);
|
|
46
|
-
|
|
58
|
+
const backend = string(source.backend);
|
|
59
|
+
const properties = object(source.properties);
|
|
60
|
+
if (name === undefined ||
|
|
61
|
+
name.length > 256 ||
|
|
62
|
+
backend === undefined ||
|
|
63
|
+
backend.length > 256 ||
|
|
64
|
+
properties === undefined ||
|
|
65
|
+
!jsonValue(properties) ||
|
|
66
|
+
!Array.isArray(source.refs) ||
|
|
67
|
+
source.refs.length > 128) {
|
|
47
68
|
throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource ${phase} returned invalid fields`, { phase });
|
|
48
69
|
}
|
|
49
70
|
const refs = [];
|
|
50
71
|
const seen = new Set();
|
|
51
72
|
for (const ref of source.refs) {
|
|
52
|
-
if (typeof ref !== 'string' || !isCredentialRefName(ref)) {
|
|
73
|
+
if (typeof ref !== 'string' || ref.length > 256 || !isCredentialRefName(ref)) {
|
|
53
74
|
throw new MarivoEnvironmentError('subprocess-output-invalid', `Marivo datasource ${phase} returned an invalid credential reference`, { phase });
|
|
54
75
|
}
|
|
55
76
|
if (!seen.has(ref)) {
|
|
@@ -57,7 +78,21 @@ function parseDescription(value, phase) {
|
|
|
57
78
|
refs.push(ref);
|
|
58
79
|
}
|
|
59
80
|
}
|
|
60
|
-
|
|
81
|
+
const fields = object(source.fields);
|
|
82
|
+
if (!fields ||
|
|
83
|
+
typeof source.definition !== 'string' ||
|
|
84
|
+
!/^[a-f0-9]{64}$/.test(source.definition) ||
|
|
85
|
+
Object.values(fields).some((ref) => typeof ref !== 'string' || !refs.includes(ref)) ||
|
|
86
|
+
refs.some((ref) => !Object.values(fields).includes(ref)))
|
|
87
|
+
throw new Error('Invalid datasource definition projection');
|
|
88
|
+
return {
|
|
89
|
+
name,
|
|
90
|
+
backend,
|
|
91
|
+
properties: properties,
|
|
92
|
+
refs,
|
|
93
|
+
fields: fields,
|
|
94
|
+
definition: source.definition,
|
|
95
|
+
};
|
|
61
96
|
}
|
|
62
97
|
function parseInventory(stdout) {
|
|
63
98
|
const value = parseJsonObject(stdout, 'inventory');
|
|
@@ -87,7 +122,9 @@ function parseRepair(value) {
|
|
|
87
122
|
throw new TypeError('repair must be an object');
|
|
88
123
|
exactKeys(source, ['kind', 'help_target', 'action', 'snippet', 'candidates', 'preserves_evidence'], 'test');
|
|
89
124
|
if (string(source.kind) === undefined ||
|
|
90
|
-
|
|
125
|
+
object(source.help_target) === undefined ||
|
|
126
|
+
string(object(source.help_target)?.surface) === undefined ||
|
|
127
|
+
!nullableString(object(source.help_target)?.canonical_id) ||
|
|
91
128
|
typeof source.action !== 'string' ||
|
|
92
129
|
!nullableString(source.snippet) ||
|
|
93
130
|
!Array.isArray(source.candidates) ||
|
|
@@ -160,6 +197,55 @@ export class MarivoDatasourceBridge {
|
|
|
160
197
|
this.#runner = runner;
|
|
161
198
|
this.binding = runner.binding;
|
|
162
199
|
}
|
|
200
|
+
async authoring(signal) {
|
|
201
|
+
const result = await this.#runner.runChecked({
|
|
202
|
+
program: DATASOURCE_AUTHORING_PROGRAM,
|
|
203
|
+
stdin: JSON.stringify({ action: 'schema' }),
|
|
204
|
+
limits: DATASOURCE_LIMITS,
|
|
205
|
+
signal,
|
|
206
|
+
});
|
|
207
|
+
this.#assertSuccess(result, 'inventory');
|
|
208
|
+
return datasourceAuthoringSchema.parse({
|
|
209
|
+
...parseJsonObject(result.stdout, 'inventory'),
|
|
210
|
+
fingerprint: this.binding.fingerprint,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async create(input, signal) {
|
|
214
|
+
const refs = Object.entries(input.fields)
|
|
215
|
+
.filter(([key]) => key.endsWith('_env'))
|
|
216
|
+
.flatMap(([, value]) => typeof value === 'string'
|
|
217
|
+
? [value]
|
|
218
|
+
: value && typeof value === 'object' && !Array.isArray(value)
|
|
219
|
+
? Object.values(value)
|
|
220
|
+
: []);
|
|
221
|
+
for (const ref of refs) {
|
|
222
|
+
if (typeof ref !== 'string')
|
|
223
|
+
return { error: 'datasource-credential-ref-invalid' };
|
|
224
|
+
try {
|
|
225
|
+
marivoCredentialStorageRef(ref);
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (error instanceof MarivoEnvironmentError &&
|
|
229
|
+
error.code === 'datasource-credential-ref-invalid')
|
|
230
|
+
return { error: 'datasource-credential-ref-invalid' };
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const result = await this.#runner.runChecked({
|
|
235
|
+
program: DATASOURCE_AUTHORING_PROGRAM,
|
|
236
|
+
stdin: JSON.stringify({ action: 'create', ...input, refs }),
|
|
237
|
+
limits: DATASOURCE_LIMITS,
|
|
238
|
+
signal,
|
|
239
|
+
});
|
|
240
|
+
this.#assertSuccess(result, 'inventory');
|
|
241
|
+
const value = parseJsonObject(result.stdout, 'inventory');
|
|
242
|
+
if (value.error === 'datasource-already-exists' ||
|
|
243
|
+
value.error === 'datasource-definition-invalid')
|
|
244
|
+
return { error: value.error };
|
|
245
|
+
if (typeof value.name !== 'string')
|
|
246
|
+
throw new Error('Invalid datasource creation result');
|
|
247
|
+
return { name: value.name };
|
|
248
|
+
}
|
|
163
249
|
async describe(name, signal) {
|
|
164
250
|
const result = await this.#runner.runChecked({
|
|
165
251
|
program: MARIVO_DATASOURCE_DESCRIBE_PROGRAM,
|
|
@@ -179,16 +265,22 @@ export class MarivoDatasourceBridge {
|
|
|
179
265
|
this.#assertSuccess(result, 'inventory');
|
|
180
266
|
return parseInventory(result.stdout);
|
|
181
267
|
}
|
|
182
|
-
async test(
|
|
268
|
+
async test(description, values, signal) {
|
|
269
|
+
const { name } = description;
|
|
183
270
|
const result = await this.#runner.runChecked({
|
|
184
271
|
program: MARIVO_DATASOURCE_TEST_PROGRAM,
|
|
185
272
|
args: [name],
|
|
186
|
-
|
|
273
|
+
stdin: JSON.stringify({
|
|
274
|
+
project_root: this.binding.projectRoot,
|
|
275
|
+
grants: { [name]: description },
|
|
276
|
+
values,
|
|
277
|
+
}),
|
|
278
|
+
secretValues: Object.values(values),
|
|
187
279
|
limits: DATASOURCE_LIMITS,
|
|
188
280
|
signal,
|
|
189
281
|
});
|
|
190
282
|
this.#assertSuccess(result, 'test');
|
|
191
|
-
return parseTest(result.stdout, Object.values(
|
|
283
|
+
return parseTest(result.stdout, Object.values(values));
|
|
192
284
|
}
|
|
193
285
|
#assertSuccess(result, phase) {
|
|
194
286
|
if (result.exitCode === 0)
|
package/lib/datasource/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export { createMarivoDatasourceAccessTool, MARIVO_DATASOURCE_ACCESS_TOOL_NAME, registerMarivoDatasourceAccessTool, } from "./access.js";
|
|
2
1
|
export { MarivoDatasourceBridge, resolveMarivoDatasourceBridge, resolveMarivoDatasourceInventoryBridge, } from "./bridge.js";
|
|
3
|
-
export {
|
|
2
|
+
export { registerMarivoPythonTool } from "./python.js";
|
|
3
|
+
export { MarivoCredentialService } from "./service.js";
|
|
4
|
+
export { assertMarivoCredentialReferences, MARIVO_CREDENTIAL_STORAGE_PREFIX, marivoCredentialStorageRef, } from "./shell-env.js";
|
|
4
5
|
export { createMarivoDatasourceTestTool, MARIVO_DATASOURCE_TEST_TOOL_NAME, registerMarivoDatasourceTestTool, } from "./test.js";
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import process from 'node:process';
|
|
2
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
3
|
+
import { savePythonExecution } from "../python-execution.js";
|
|
4
|
+
import { resolveMarivoDatasourceBridge } from "./bridge.js";
|
|
5
|
+
import { PYTHON_LAUNCHER, PYTHON_WORKER } from "./resolver-program.js";
|
|
6
|
+
import { datasourceToolValue } from "./test.js";
|
|
7
|
+
function quote(value) {
|
|
8
|
+
return process.platform === 'win32'
|
|
9
|
+
? `'${value.replaceAll("'", "''")}'`
|
|
10
|
+
: `'${value.replaceAll("'", "'\\''")}'`;
|
|
11
|
+
}
|
|
12
|
+
export function registerMarivoPythonTool(ctx, source, service) {
|
|
13
|
+
return ctx.tools.register(defineTool({
|
|
14
|
+
name: 'marivo_python',
|
|
15
|
+
description: 'Execute foreground Python in the bound Marivo Workspace using Host-injected credentials. This call waits for missing credentials for every listed datasource before starting Python once; configured credentials do not trigger a connection test. Create/resume Sessions and readers inside this execution; close Sessions in finally. No background execution or secret environment variables. Nonzero exits are reported without replay. Successful execution saves the exact submitted code and returns codeRef; explicitly associate it with presentation datasets through draft codeRefs. A codeCaptureError does not change the execution outcome and must not trigger replay.',
|
|
16
|
+
parameters: {
|
|
17
|
+
code: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
required: true,
|
|
20
|
+
description: 'Python code, never credential values. Use try/finally for per-call Session resource cleanup; consult current Runtime Help for Session APIs.',
|
|
21
|
+
},
|
|
22
|
+
datasources: {
|
|
23
|
+
type: 'array',
|
|
24
|
+
required: true,
|
|
25
|
+
items: { type: 'string' },
|
|
26
|
+
description: 'All exact datasource names this execution may access through data or metadata connections, including inspection and datasources without passwords; pass [] only when no datasource will be accessed. Catalog-only definition reads need no datasource connection.',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
output: {
|
|
30
|
+
schema: { type: 'json' },
|
|
31
|
+
render: (_args, value) => [{ type: 'text', text: JSON.stringify(value) }],
|
|
32
|
+
},
|
|
33
|
+
async execute(args, exec) {
|
|
34
|
+
const code = args.code;
|
|
35
|
+
if (!code.trim() || Buffer.byteLength(code) > 131072)
|
|
36
|
+
throw new Error('Invalid Python code size');
|
|
37
|
+
const shell = ctx.get('shell');
|
|
38
|
+
if (!shell)
|
|
39
|
+
throw new Error('DSH Shell execution service is required');
|
|
40
|
+
const shellEnv = ctx.get('shellEnv');
|
|
41
|
+
if (!shellEnv)
|
|
42
|
+
throw new Error('DSH Shell environment service is required');
|
|
43
|
+
const policyService = ctx.get('sandboxPolicy');
|
|
44
|
+
if (shell.sandboxMode !== undefined && !policyService)
|
|
45
|
+
throw new Error('DSH sandbox policy is required');
|
|
46
|
+
const prepared = await service.track(service.prepareExecution(exec, () => resolveMarivoDatasourceBridge(source), args.datasources));
|
|
47
|
+
if (!('status' in prepared) || prepared.status !== 'ready')
|
|
48
|
+
return datasourceToolValue(prepared);
|
|
49
|
+
try {
|
|
50
|
+
const { binding } = prepared.bridge;
|
|
51
|
+
const policy = policyService?.resolve(exec.agent ? { session: exec.agent.session } : {});
|
|
52
|
+
const spec = shell.resolve({
|
|
53
|
+
command: `${process.platform === 'win32' ? '& ' : ''}${quote(binding.pythonExecutable)} -c ${quote(PYTHON_LAUNCHER)}`,
|
|
54
|
+
workdir: binding.projectRoot,
|
|
55
|
+
timeoutMs: 120_000,
|
|
56
|
+
signal: prepared.signal,
|
|
57
|
+
stdin: JSON.stringify({
|
|
58
|
+
identity: binding,
|
|
59
|
+
project_root: binding.projectRoot,
|
|
60
|
+
grants: prepared.grants,
|
|
61
|
+
values: prepared.values,
|
|
62
|
+
code,
|
|
63
|
+
worker: PYTHON_WORKER,
|
|
64
|
+
}),
|
|
65
|
+
env: { MARIVO_PERSIST_CREDENTIALS: '0' },
|
|
66
|
+
dshEnv: shellEnv.collect(exec),
|
|
67
|
+
...(policy ? { sandboxPolicy: policy } : {}),
|
|
68
|
+
});
|
|
69
|
+
// Keep this guard adjacent to the only launch, after synchronous Host hooks.
|
|
70
|
+
prepared.assertCurrent();
|
|
71
|
+
const startedAt = new Date().toISOString();
|
|
72
|
+
const result = await service.track(shell.run(spec));
|
|
73
|
+
const finishedAt = new Date().toISOString();
|
|
74
|
+
// Defense at the result seam as well as before Harness collection/spill.
|
|
75
|
+
const redact = (text) => Object.values(prepared.values)
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
.sort((a, b) => b.length - a.length)
|
|
78
|
+
.reduce((out, value) => out.split(value).join('[REDACTED]'), text);
|
|
79
|
+
let codeRef;
|
|
80
|
+
let codeCaptureError;
|
|
81
|
+
if (result.exitCode === 0 && !result.timedOut && !result.aborted) {
|
|
82
|
+
try {
|
|
83
|
+
prepared.assertCurrent();
|
|
84
|
+
if (Object.values(prepared.values).some((value) => value && code.includes(value)))
|
|
85
|
+
throw new Error('Host credential in submitted code');
|
|
86
|
+
codeRef = await service.track(savePythonExecution(binding.projectRoot, { text: code, startedAt, finishedAt }, prepared.signal));
|
|
87
|
+
prepared.assertCurrent();
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
codeRef = undefined;
|
|
91
|
+
codeCaptureError =
|
|
92
|
+
'Python completed successfully, but its source code could not be saved. Do not rerun the code to recover this snapshot; inspect its existing effects.';
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return JSON.parse(JSON.stringify({
|
|
96
|
+
exitCode: result.exitCode,
|
|
97
|
+
timedOut: result.timedOut,
|
|
98
|
+
aborted: result.aborted,
|
|
99
|
+
stdout: redact(result.stdout.text),
|
|
100
|
+
stderr: redact(result.stderr.text),
|
|
101
|
+
truncated: result.stdout.truncated || result.stderr.truncated,
|
|
102
|
+
sandbox: result.sandbox ?? null,
|
|
103
|
+
...(codeRef ? { codeRef } : {}),
|
|
104
|
+
...(codeCaptureError ? { codeCaptureError } : {}),
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
throw new Error('Marivo Python execution failed or was cancelled; inspect the execution policy and retry only after checking effects');
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
prepared.release();
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/** Shared Python adapter. All inputs below arrive on a Host-owned pipe. */
|
|
2
|
+
export const RESOLVER_PROGRAM = String.raw `
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import marivo
|
|
9
|
+
import marivo.datasource as md
|
|
10
|
+
|
|
11
|
+
if not all(hasattr(md, name) for name in ("credential_scope", "CredentialRequest", "SecretValue", "DatasourceCredentialError")):
|
|
12
|
+
raise RuntimeError("Marivo credential_scope capability is required; upgrade the bound Runtime")
|
|
13
|
+
|
|
14
|
+
def definition(description):
|
|
15
|
+
return hashlib.sha256(json.dumps({
|
|
16
|
+
"name": description.name, "backend": description.backend_type,
|
|
17
|
+
"fields": description.literal_fields, "refs": description.env_refs,
|
|
18
|
+
}, sort_keys=True, default=str).encode()).hexdigest()
|
|
19
|
+
|
|
20
|
+
class SnapshotResolver:
|
|
21
|
+
def __init__(self, payload):
|
|
22
|
+
self.root = Path(payload["project_root"]).resolve()
|
|
23
|
+
self.grants = payload["grants"]
|
|
24
|
+
self.values = payload["values"]
|
|
25
|
+
|
|
26
|
+
def resolve(self, request):
|
|
27
|
+
grant = self.grants.get(request.datasource)
|
|
28
|
+
if request.cancelled:
|
|
29
|
+
reason = "timeout"
|
|
30
|
+
elif Path(request.project_root).resolve() != self.root or grant is None:
|
|
31
|
+
reason = "denied"
|
|
32
|
+
elif any(grant["fields"].get(field) != request.reference for field in request.fields):
|
|
33
|
+
reason = "denied"
|
|
34
|
+
elif not request.fields or request.reference not in grant["fields"].values():
|
|
35
|
+
reason = "denied"
|
|
36
|
+
elif definition(md.describe(request.datasource)) != grant["definition"]:
|
|
37
|
+
reason = "denied"
|
|
38
|
+
elif not self.values.get(request.reference):
|
|
39
|
+
reason = "missing"
|
|
40
|
+
else:
|
|
41
|
+
return md.SecretValue(self.values[request.reference])
|
|
42
|
+
raise md.DatasourceCredentialError(reason=reason, reference=request.reference,
|
|
43
|
+
datasource=request.datasource, fields=request.fields)
|
|
44
|
+
`.trim();
|
|
45
|
+
/** The worker checks the exact binding and enters scope before any user code. */
|
|
46
|
+
export const PYTHON_WORKER = `${RESOLVER_PROGRAM}\n${String.raw `
|
|
47
|
+
payload = json.load(sys.stdin)
|
|
48
|
+
expected = payload["identity"]
|
|
49
|
+
if (os.path.abspath(sys.executable) != expected["pythonExecutable"] or
|
|
50
|
+
marivo.__version__ != expected["marivoVersion"] or
|
|
51
|
+
os.path.abspath(marivo.__file__) != expected["packagePath"] or
|
|
52
|
+
str(Path.cwd().resolve()) != payload["project_root"]):
|
|
53
|
+
raise RuntimeError("Marivo execution identity changed; rebind required")
|
|
54
|
+
resolver = SnapshotResolver(payload)
|
|
55
|
+
code = payload.pop("code")
|
|
56
|
+
sys.stdin = open(os.devnull)
|
|
57
|
+
try:
|
|
58
|
+
with md.credential_scope(resolver=resolver):
|
|
59
|
+
for name, grant in resolver.grants.items():
|
|
60
|
+
description = md.describe(name)
|
|
61
|
+
if grant["name"] != name or description.name != name:
|
|
62
|
+
raise RuntimeError("Marivo datasource identity changed; execution cancelled")
|
|
63
|
+
if definition(description) != grant["definition"]:
|
|
64
|
+
raise RuntimeError("Marivo datasource definition changed; execution cancelled")
|
|
65
|
+
exec(compile(code, "<marivo_python>", "exec"), {"__name__": "__main__"})
|
|
66
|
+
finally:
|
|
67
|
+
resolver.values.clear()
|
|
68
|
+
payload.clear()
|
|
69
|
+
`}`;
|
|
70
|
+
/** Capture raw fd output before Harness collection/spill, including driver writes. */
|
|
71
|
+
export const PYTHON_LAUNCHER = String.raw `
|
|
72
|
+
import json, subprocess, sys, threading
|
|
73
|
+
payload = json.load(sys.stdin)
|
|
74
|
+
worker = payload.pop("worker")
|
|
75
|
+
values = sorted(set(payload["values"].values()), key=len, reverse=True)
|
|
76
|
+
child = subprocess.Popen([sys.executable, "-c", worker], stdin=subprocess.PIPE,
|
|
77
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
78
|
+
streams = [bytearray(), bytearray()]
|
|
79
|
+
overflow = threading.Event()
|
|
80
|
+
def collect(pipe, target):
|
|
81
|
+
while True:
|
|
82
|
+
chunk = pipe.read(8192)
|
|
83
|
+
if not chunk:
|
|
84
|
+
break
|
|
85
|
+
if len(target) + len(chunk) > 1048576:
|
|
86
|
+
overflow.set()
|
|
87
|
+
child.kill()
|
|
88
|
+
elif not overflow.is_set():
|
|
89
|
+
target.extend(chunk)
|
|
90
|
+
threads = [threading.Thread(target=collect, args=(pipe, buf), daemon=True)
|
|
91
|
+
for pipe, buf in zip((child.stdout, child.stderr), streams)]
|
|
92
|
+
for thread in threads:
|
|
93
|
+
thread.start()
|
|
94
|
+
try:
|
|
95
|
+
child.stdin.write(json.dumps(payload).encode())
|
|
96
|
+
child.stdin.close()
|
|
97
|
+
except BrokenPipeError:
|
|
98
|
+
pass
|
|
99
|
+
child.wait()
|
|
100
|
+
for thread in threads:
|
|
101
|
+
thread.join()
|
|
102
|
+
if overflow.is_set():
|
|
103
|
+
print("Marivo Python output limit exceeded", file=sys.stderr)
|
|
104
|
+
raise SystemExit(74)
|
|
105
|
+
for stream, raw in zip((sys.stdout, sys.stderr), streams):
|
|
106
|
+
text = raw.decode("utf-8", errors="replace")
|
|
107
|
+
for value in values:
|
|
108
|
+
if value:
|
|
109
|
+
text = text.replace(value, "[REDACTED]")
|
|
110
|
+
stream.write(text)
|
|
111
|
+
raise SystemExit(child.returncode if child.returncode >= 0 else 1)
|
|
112
|
+
`.trim();
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { CREDENTIAL_CHANNEL, credentialError } from "./service.js";
|
|
3
|
+
const text = z.string().min(1).max(256);
|
|
4
|
+
const handle = { generation: z.string().uuid(), id: z.string().uuid(), scope: z.string().uuid() };
|
|
5
|
+
const mutation = z
|
|
6
|
+
.object({
|
|
7
|
+
...handle,
|
|
8
|
+
action: z.enum(['save', 'update', 'delete', 'test', 'submit', 'diagnose']),
|
|
9
|
+
version: z.string().max(65536),
|
|
10
|
+
requestId: z.string().uuid().optional(),
|
|
11
|
+
reference: text.optional(),
|
|
12
|
+
changes: z.record(text, z.string().min(1).max(65536)).optional(),
|
|
13
|
+
})
|
|
14
|
+
.strict();
|
|
15
|
+
export function registerCredentialRpc(connection, service, workspace) {
|
|
16
|
+
const unregister = connection.rpc.handle(CREDENTIAL_CHANNEL, async (endpoint, payload, signal) => {
|
|
17
|
+
try {
|
|
18
|
+
if (Buffer.byteLength(JSON.stringify(payload) ?? '') > 1_048_576)
|
|
19
|
+
throw new Error('invalid-request');
|
|
20
|
+
let value;
|
|
21
|
+
if (endpoint === 'overview') {
|
|
22
|
+
const input = z.object({ workspaceId: text }).strict().parse(payload);
|
|
23
|
+
value = {
|
|
24
|
+
generation: service.generation,
|
|
25
|
+
datasources: await service.overview(input.workspaceId, () => workspace(input.workspaceId), signal),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
else if (endpoint === 'authoring') {
|
|
29
|
+
const input = z.object({ workspaceId: text }).strict().parse(payload);
|
|
30
|
+
const bridge = await workspace(input.workspaceId);
|
|
31
|
+
if (!bridge.authoring)
|
|
32
|
+
throw new Error('authoring-unavailable');
|
|
33
|
+
value = { generation: service.generation, ...(await bridge.authoring(signal)) };
|
|
34
|
+
}
|
|
35
|
+
else if (endpoint === 'create-datasource') {
|
|
36
|
+
const input = z
|
|
37
|
+
.object({
|
|
38
|
+
workspaceId: text,
|
|
39
|
+
generation: z.string().uuid(),
|
|
40
|
+
fingerprint: z.string().min(1).max(256),
|
|
41
|
+
backend: text,
|
|
42
|
+
fields: z.record(text, z.unknown()),
|
|
43
|
+
})
|
|
44
|
+
.strict()
|
|
45
|
+
.parse(payload);
|
|
46
|
+
value = await service.createDatasource(input.generation, input.fingerprint, input, () => workspace(input.workspaceId), signal);
|
|
47
|
+
}
|
|
48
|
+
else if (endpoint === 'watch') {
|
|
49
|
+
const input = z
|
|
50
|
+
.object({ sessionId: text, cursor: z.string().max(256).optional() })
|
|
51
|
+
.strict()
|
|
52
|
+
.parse(payload);
|
|
53
|
+
value = await service.waitWatch(input.sessionId, input.cursor, signal);
|
|
54
|
+
}
|
|
55
|
+
else if (endpoint === 'start') {
|
|
56
|
+
value = service.start(mutation.parse(payload));
|
|
57
|
+
}
|
|
58
|
+
else if (endpoint === 'operation') {
|
|
59
|
+
const input = z.object(handle).strict().parse(payload);
|
|
60
|
+
value = service.operation(input.generation, input.id, input.scope) ?? null;
|
|
61
|
+
}
|
|
62
|
+
else if (endpoint === 'cancel-operation') {
|
|
63
|
+
const input = z.object(handle).strict().parse(payload);
|
|
64
|
+
service.cancelOperation(input.generation, input.id, input.scope);
|
|
65
|
+
value = { cancelled: true };
|
|
66
|
+
}
|
|
67
|
+
else if (endpoint === 'cancel-request') {
|
|
68
|
+
const input = z.object({ requestId: z.string().uuid() }).strict().parse(payload);
|
|
69
|
+
service.cancelRequest(input.requestId);
|
|
70
|
+
value = { cancelled: true };
|
|
71
|
+
}
|
|
72
|
+
else
|
|
73
|
+
throw new Error('unknown-endpoint');
|
|
74
|
+
return { ok: true, value };
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
return {
|
|
78
|
+
ok: false,
|
|
79
|
+
error: { code: 'internal', message: credentialError(error), details: {} },
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}, { authority: 'trusted-host' });
|
|
83
|
+
return async () => {
|
|
84
|
+
const draining = unregister();
|
|
85
|
+
await service.close();
|
|
86
|
+
await draining;
|
|
87
|
+
};
|
|
88
|
+
}
|