@ai-agent-forge/plugin-lsp-diagnostics 0.85.0
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 +41 -0
- package/agent-forge.json +12 -0
- package/dist/capability.d.ts +5 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +185 -0
- package/dist/capability.js.map +1 -0
- package/dist/entry.d.ts +12 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +13 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/node-adapter.d.ts +13 -0
- package/dist/node-adapter.d.ts.map +1 -0
- package/dist/node-adapter.js +404 -0
- package/dist/node-adapter.js.map +1 -0
- package/dist/node-lsp-real-host.d.ts +38 -0
- package/dist/node-lsp-real-host.d.ts.map +1 -0
- package/dist/node-lsp-real-host.js +396 -0
- package/dist/node-lsp-real-host.js.map +1 -0
- package/dist/shell-utils.d.ts +15 -0
- package/dist/shell-utils.d.ts.map +1 -0
- package/dist/shell-utils.js +43 -0
- package/dist/shell-utils.js.map +1 -0
- package/dist/types.d.ts +105 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/package.json +55 -0
- package/plugin.json +9 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @agent-forge/plugin-lsp-diagnostics
|
|
2
|
+
|
|
3
|
+
LSP diagnostics capability plugin for [Agent Forge](https://github.com/agent-forge/agent-forge), extracted from the
|
|
4
|
+
host's `src/plugins/lsp` (D-075 S3). It registers a single read-only tool, `lsp_diagnostics`, which asks the host to
|
|
5
|
+
read bounded diagnostics from a language-server session using opaque workspace/document references.
|
|
6
|
+
|
|
7
|
+
The plugin has no standalone language-server binding of its own at load time. Without host-injected resolver/client
|
|
8
|
+
seams the tool reports an explicit `unsupported` result; hosts that want real diagnostics bind
|
|
9
|
+
`createNodeLspRealHostV1` (tsserver/pyright/clangd over one workspace directory) or their own client.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
# from a local checkout
|
|
15
|
+
agent-forge plugin install ./plugins/lsp-diagnostics
|
|
16
|
+
|
|
17
|
+
# from npm
|
|
18
|
+
agent-forge plugin install @agent-forge/plugin-lsp-diagnostics
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The package ships two manifests: `agent-forge.json` (ecosystem manifest for the deterministic plugin resolver) and
|
|
22
|
+
`plugin.json` (host-load manifest consumed by `loadCapabilityPluginFromManifest`, entry `./dist/entry.js`).
|
|
23
|
+
|
|
24
|
+
## Library surface
|
|
25
|
+
|
|
26
|
+
`import { createNodeLspAdapterV1, createLspDiagnosticsCapabilityFactoryV1 } from "@agent-forge/plugin-lsp-diagnostics";`
|
|
27
|
+
|
|
28
|
+
- `createNodeLspAdapterV1(options?)` — read-only adapter; requires host-injected `resolver`/`client` to produce
|
|
29
|
+
results, otherwise returns `unsupported`.
|
|
30
|
+
- `createDeterministicLspAdapterV1(fixtures)` — deterministic fixture adapter for tests.
|
|
31
|
+
- `createNodeLspRealHostV1({ workspaceDirectory, ... })` — real language-server host (tsserver/pyright/clangd).
|
|
32
|
+
- `createLspDiagnosticsCapabilityFactoryV1(adapter)` — registers `lsp_diagnostics` on a `CapabilityAPI`.
|
|
33
|
+
- Default export of `dist/entry.js` — the precomposed factory used by external plugin loading.
|
|
34
|
+
|
|
35
|
+
## Development
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
npm install --ignore-scripts # repo root, creates the workspace symlink
|
|
39
|
+
npm run build # plugins/lsp-diagnostics (tsgo)
|
|
40
|
+
npm test # vitest; the entry fidelity test requires `npm run build` first
|
|
41
|
+
```
|
package/agent-forge.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packageFormatVersion": 1,
|
|
3
|
+
"id": "agent-forge.plugin.lsp-diagnostics",
|
|
4
|
+
"pluginVersion": "0.84.4",
|
|
5
|
+
"apiVersion": "1",
|
|
6
|
+
"minHostVersion": "0.84.4",
|
|
7
|
+
"capabilityRefs": [{ "id": "lsp.diagnostics", "version": 1 }],
|
|
8
|
+
"platform": {
|
|
9
|
+
"os": ["windows", "macos", "linux"],
|
|
10
|
+
"runtime": [{ "name": "node" }]
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { CapabilityPluginSyncFactory } from "@agent-forge/plugin-sdk";
|
|
2
|
+
import type { LspDiagnosticsAdapterV1 } from "./types.ts";
|
|
3
|
+
/** Registers read-only language diagnostics using only host-owned refs and a host client. */
|
|
4
|
+
export declare function createLspDiagnosticsCapabilityFactoryV1(runtime: LspDiagnosticsAdapterV1): CapabilityPluginSyncFactory;
|
|
5
|
+
//# sourceMappingURL=capability.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.d.ts","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAO3E,OAAO,KAAK,EAAE,uBAAuB,EAA0B,MAAM,YAAY,CAAC;AAuKlF,6FAA6F;AAC7F,wBAAgB,uCAAuC,CAAC,OAAO,EAAE,uBAAuB,GAAG,2BAA2B,CAiDrH","sourcesContent":["import type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport {\n\tLSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1,\n\tprojectLspDiagnosticsResultV1,\n\tsanitizeLspDiagnosticsTextV1,\n} from \"./node-adapter.ts\";\nimport type { LspDiagnosticsAdapterV1, LspDiagnosticsResultV1 } from \"./types.ts\";\n\nconst requestSchema = Type.Object(\n\t{\n\t\tworkspaceRef: Type.String({ minLength: 1, maxLength: 256, pattern: \"^[A-Za-z0-9][A-Za-z0-9._:-]*$\" }),\n\t\tlanguage: Type.String({ minLength: 1, maxLength: 64, pattern: \"^[A-Za-z][A-Za-z0-9_.+-]*$\" }),\n\t\tdocumentRef: Type.String({ minLength: 1, maxLength: 256, pattern: \"^[A-Za-z0-9][A-Za-z0-9._:-]*$\" }),\n\t\tdocumentPath: Type.Optional(\n\t\t\tType.String({ minLength: 1, maxLength: 512, pattern: \"^[A-Za-z0-9_][A-Za-z0-9._/-]*$\" }),\n\t\t),\n\t\tdiagnosticsLimit: Type.Optional(Type.Integer({ minimum: 1, maximum: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = second === undefined ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"LSP input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction invocation(\n\tfirst: unknown,\n\tsecond: unknown,\n): { readonly input: Record<string, unknown>; readonly operationId: string } {\n\tconst candidate = typeof first === \"string\" && first.trim() !== \"\" ? first : \"profile-runtime-tool-call\";\n\treturn {\n\t\tinput: objectInput(first, second),\n\t\toperationId:\n\t\t\tcandidate.length <= LSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1 ? candidate : \"profile-runtime-tool-call\",\n\t};\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction emptySummary(): LspDiagnosticsResultV1[\"summary\"] {\n\treturn { total: 0, error: 0, warning: 0, info: 0, hint: 0 };\n}\n\nfunction cancelled(operationId: string): LspDiagnosticsResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"cancelled\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"cancelled\", message: \"LSP diagnostics were cancelled\" },\n\t};\n}\n\nfunction adapterFailed(operationId: string): LspDiagnosticsResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"adapter_failed\", message: \"LSP adapter returned an invalid result\" },\n\t};\n}\n\nfunction validSummary(value: unknown): boolean {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst summary = value as Record<string, unknown>;\n\tconst keys = [\"total\", \"error\", \"warning\", \"info\", \"hint\"];\n\tif (Object.keys(summary).some((key) => !keys.includes(key)) || keys.some((key) => !(key in summary))) return false;\n\tconst counts = keys.map((key) => summary[key]);\n\tif (counts.some((count) => !Number.isSafeInteger(count) || (count as number) < 0 || (count as number) > 10_000_000))\n\t\treturn false;\n\treturn (counts[1] as number) + (counts[2] as number) + (counts[3] as number) + (counts[4] as number) === counts[0];\n}\n\nfunction validError(value: unknown): boolean {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst error = value as Record<string, unknown>;\n\tconst keys = [\"version\", \"code\", \"message\", \"causeCode\"];\n\tif (Object.keys(error).some((key) => !keys.includes(key))) return false;\n\tif (error.version !== 1 || typeof error.code !== \"string\" || !/^[a-z][a-z0-9_]{0,127}$/u.test(error.code))\n\t\treturn false;\n\tif (typeof error.message !== \"string\" || error.message.trim() === \"\" || error.message.length > 512) return false;\n\treturn (\n\t\terror.causeCode === undefined ||\n\t\t(typeof error.causeCode === \"string\" && /^[a-z][a-z0-9_]{0,127}$/u.test(error.causeCode))\n\t);\n}\n\nfunction validReportArtifactRef(value: unknown): value is string {\n\treturn value === undefined || (typeof value === \"string\" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u.test(value));\n}\n\nfunction boundedResult(value: unknown, operationId: string): LspDiagnosticsResultV1 {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return adapterFailed(operationId);\n\tconst result = value as Record<string, unknown>;\n\tif (result.status === \"succeeded\")\n\t\treturn projectLspDiagnosticsResultV1(result, operationId) ?? adapterFailed(operationId);\n\tif (\n\t\tresult.version !== 1 ||\n\t\tresult.operationId !== operationId ||\n\t\t![\"failed\", \"cancelled\", \"needs_user\", \"unsupported\"].includes(result.status as string) ||\n\t\t!Array.isArray(result.diagnostics) ||\n\t\tresult.diagnostics.length !== 0 ||\n\t\tresult.diagnosticsTruncated !== false ||\n\t\tresult.error === undefined\n\t)\n\t\treturn adapterFailed(operationId);\n\tif (\n\t\tObject.keys(result).some(\n\t\t\t(key) =>\n\t\t\t\t![\n\t\t\t\t\t\"version\",\n\t\t\t\t\t\"operationId\",\n\t\t\t\t\t\"status\",\n\t\t\t\t\t\"summary\",\n\t\t\t\t\t\"diagnostics\",\n\t\t\t\t\t\"diagnosticsTruncated\",\n\t\t\t\t\t\"reportArtifactRef\",\n\t\t\t\t\t\"error\",\n\t\t\t\t].includes(key),\n\t\t)\n\t)\n\t\treturn adapterFailed(operationId);\n\tif (!validSummary(result.summary) || !validError(result.error) || !validReportArtifactRef(result.reportArtifactRef))\n\t\treturn adapterFailed(operationId);\n\tconst error = result.error as {\n\t\treadonly version: 1;\n\t\treadonly code: string;\n\t\treadonly message: string;\n\t\treadonly causeCode?: string;\n\t};\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: result.status as LspDiagnosticsResultV1[\"status\"],\n\t\tsummary: result.summary as LspDiagnosticsResultV1[\"summary\"],\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\t...(result.reportArtifactRef === undefined ? {} : { reportArtifactRef: result.reportArtifactRef }),\n\t\terror: {\n\t\t\tversion: 1,\n\t\t\tcode: error.code,\n\t\t\tmessage: sanitizeLspDiagnosticsTextV1(error.message),\n\t\t\t...(error.causeCode === undefined ? {} : { causeCode: error.causeCode }),\n\t\t},\n\t};\n}\n\nconst CANCELLATION_V1 = Symbol(\"lsp-capability-cancelled\");\n\nfunction runWithCancellation(\n\trun: () => Promise<LspDiagnosticsResultV1>,\n\tsignal: AbortSignal | undefined,\n): Promise<LspDiagnosticsResultV1 | typeof CANCELLATION_V1> {\n\tif (signal === undefined) return run();\n\tlet onAbort!: () => void;\n\tconst cancellation = new Promise<typeof CANCELLATION_V1>((resolve) => {\n\t\tonAbort = () => resolve(CANCELLATION_V1);\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n\tconst pending = run();\n\treturn Promise.race([pending, cancellation]).finally(() => signal.removeEventListener(\"abort\", onAbort));\n}\n\n/** Registers read-only language diagnostics using only host-owned refs and a host client. */\nexport function createLspDiagnosticsCapabilityFactoryV1(runtime: LspDiagnosticsAdapterV1): CapabilityPluginSyncFactory {\n\treturn (api) => {\n\t\tapi.registerTool({\n\t\t\tname: \"lsp_diagnostics\",\n\t\t\tlabel: \"LSP diagnostics\",\n\t\t\tdescription: \"Read bounded diagnostics from a host-bound language server\",\n\t\t\tparameters: Type.Object({ request: requestSchema }, { additionalProperties: false }),\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet invocationResult: { readonly input: Record<string, unknown>; readonly operationId: string };\n\t\t\t\ttry {\n\t\t\t\t\tinvocationResult = invocation(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn adapterFailed(\"profile-runtime-tool-call\");\n\t\t\t\t}\n\t\t\t\tconst { input, operationId } = invocationResult;\n\t\t\t\tconst signal = signalFrom(third);\n\t\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\t\tlet requestInput: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\trequestInput = objectInput(input.request, undefined);\n\t\t\t\t} catch {\n\t\t\t\t\treturn adapterFailed(operationId);\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst output = await runWithCancellation(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\truntime.run(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t\t\tworkspaceRef: requestInput.workspaceRef as string,\n\t\t\t\t\t\t\t\t\tlanguage: requestInput.language as string,\n\t\t\t\t\t\t\t\t\tdocumentRef: requestInput.documentRef as string,\n\t\t\t\t\t\t\t\t\t...(requestInput.diagnosticsLimit === undefined\n\t\t\t\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t\t\t\t: { diagnosticsLimit: requestInput.diagnosticsLimit as number }),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t);\n\t\t\t\t\tif (output === CANCELLATION_V1) return cancelled(operationId);\n\t\t\t\t\treturn boundedResult(output, operationId);\n\t\t\t\t} catch {\n\t\t\t\t\treturn adapterFailed(operationId);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Type } from "typebox";
|
|
2
|
+
import { LSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1, projectLspDiagnosticsResultV1, sanitizeLspDiagnosticsTextV1, } from "./node-adapter.js";
|
|
3
|
+
const requestSchema = Type.Object({
|
|
4
|
+
workspaceRef: Type.String({ minLength: 1, maxLength: 256, pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$" }),
|
|
5
|
+
language: Type.String({ minLength: 1, maxLength: 64, pattern: "^[A-Za-z][A-Za-z0-9_.+-]*$" }),
|
|
6
|
+
documentRef: Type.String({ minLength: 1, maxLength: 256, pattern: "^[A-Za-z0-9][A-Za-z0-9._:-]*$" }),
|
|
7
|
+
documentPath: Type.Optional(Type.String({ minLength: 1, maxLength: 512, pattern: "^[A-Za-z0-9_][A-Za-z0-9._/-]*$" })),
|
|
8
|
+
diagnosticsLimit: Type.Optional(Type.Integer({ minimum: 1, maximum: 128 })),
|
|
9
|
+
}, { additionalProperties: false });
|
|
10
|
+
function objectInput(first, second) {
|
|
11
|
+
const value = second === undefined ? first : second;
|
|
12
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
13
|
+
throw new Error("LSP input must be an object");
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function invocation(first, second) {
|
|
17
|
+
const candidate = typeof first === "string" && first.trim() !== "" ? first : "profile-runtime-tool-call";
|
|
18
|
+
return {
|
|
19
|
+
input: objectInput(first, second),
|
|
20
|
+
operationId: candidate.length <= LSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1 ? candidate : "profile-runtime-tool-call",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function signalFrom(value) {
|
|
24
|
+
return value instanceof AbortSignal ? value : undefined;
|
|
25
|
+
}
|
|
26
|
+
function emptySummary() {
|
|
27
|
+
return { total: 0, error: 0, warning: 0, info: 0, hint: 0 };
|
|
28
|
+
}
|
|
29
|
+
function cancelled(operationId) {
|
|
30
|
+
return {
|
|
31
|
+
version: 1,
|
|
32
|
+
operationId,
|
|
33
|
+
status: "cancelled",
|
|
34
|
+
summary: emptySummary(),
|
|
35
|
+
diagnostics: [],
|
|
36
|
+
diagnosticsTruncated: false,
|
|
37
|
+
error: { version: 1, code: "cancelled", message: "LSP diagnostics were cancelled" },
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function adapterFailed(operationId) {
|
|
41
|
+
return {
|
|
42
|
+
version: 1,
|
|
43
|
+
operationId,
|
|
44
|
+
status: "failed",
|
|
45
|
+
summary: emptySummary(),
|
|
46
|
+
diagnostics: [],
|
|
47
|
+
diagnosticsTruncated: false,
|
|
48
|
+
error: { version: 1, code: "adapter_failed", message: "LSP adapter returned an invalid result" },
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function validSummary(value) {
|
|
52
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
53
|
+
return false;
|
|
54
|
+
const summary = value;
|
|
55
|
+
const keys = ["total", "error", "warning", "info", "hint"];
|
|
56
|
+
if (Object.keys(summary).some((key) => !keys.includes(key)) || keys.some((key) => !(key in summary)))
|
|
57
|
+
return false;
|
|
58
|
+
const counts = keys.map((key) => summary[key]);
|
|
59
|
+
if (counts.some((count) => !Number.isSafeInteger(count) || count < 0 || count > 10_000_000))
|
|
60
|
+
return false;
|
|
61
|
+
return counts[1] + counts[2] + counts[3] + counts[4] === counts[0];
|
|
62
|
+
}
|
|
63
|
+
function validError(value) {
|
|
64
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
65
|
+
return false;
|
|
66
|
+
const error = value;
|
|
67
|
+
const keys = ["version", "code", "message", "causeCode"];
|
|
68
|
+
if (Object.keys(error).some((key) => !keys.includes(key)))
|
|
69
|
+
return false;
|
|
70
|
+
if (error.version !== 1 || typeof error.code !== "string" || !/^[a-z][a-z0-9_]{0,127}$/u.test(error.code))
|
|
71
|
+
return false;
|
|
72
|
+
if (typeof error.message !== "string" || error.message.trim() === "" || error.message.length > 512)
|
|
73
|
+
return false;
|
|
74
|
+
return (error.causeCode === undefined ||
|
|
75
|
+
(typeof error.causeCode === "string" && /^[a-z][a-z0-9_]{0,127}$/u.test(error.causeCode)));
|
|
76
|
+
}
|
|
77
|
+
function validReportArtifactRef(value) {
|
|
78
|
+
return value === undefined || (typeof value === "string" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u.test(value));
|
|
79
|
+
}
|
|
80
|
+
function boundedResult(value, operationId) {
|
|
81
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
82
|
+
return adapterFailed(operationId);
|
|
83
|
+
const result = value;
|
|
84
|
+
if (result.status === "succeeded")
|
|
85
|
+
return projectLspDiagnosticsResultV1(result, operationId) ?? adapterFailed(operationId);
|
|
86
|
+
if (result.version !== 1 ||
|
|
87
|
+
result.operationId !== operationId ||
|
|
88
|
+
!["failed", "cancelled", "needs_user", "unsupported"].includes(result.status) ||
|
|
89
|
+
!Array.isArray(result.diagnostics) ||
|
|
90
|
+
result.diagnostics.length !== 0 ||
|
|
91
|
+
result.diagnosticsTruncated !== false ||
|
|
92
|
+
result.error === undefined)
|
|
93
|
+
return adapterFailed(operationId);
|
|
94
|
+
if (Object.keys(result).some((key) => ![
|
|
95
|
+
"version",
|
|
96
|
+
"operationId",
|
|
97
|
+
"status",
|
|
98
|
+
"summary",
|
|
99
|
+
"diagnostics",
|
|
100
|
+
"diagnosticsTruncated",
|
|
101
|
+
"reportArtifactRef",
|
|
102
|
+
"error",
|
|
103
|
+
].includes(key)))
|
|
104
|
+
return adapterFailed(operationId);
|
|
105
|
+
if (!validSummary(result.summary) || !validError(result.error) || !validReportArtifactRef(result.reportArtifactRef))
|
|
106
|
+
return adapterFailed(operationId);
|
|
107
|
+
const error = result.error;
|
|
108
|
+
return {
|
|
109
|
+
version: 1,
|
|
110
|
+
operationId,
|
|
111
|
+
status: result.status,
|
|
112
|
+
summary: result.summary,
|
|
113
|
+
diagnostics: [],
|
|
114
|
+
diagnosticsTruncated: false,
|
|
115
|
+
...(result.reportArtifactRef === undefined ? {} : { reportArtifactRef: result.reportArtifactRef }),
|
|
116
|
+
error: {
|
|
117
|
+
version: 1,
|
|
118
|
+
code: error.code,
|
|
119
|
+
message: sanitizeLspDiagnosticsTextV1(error.message),
|
|
120
|
+
...(error.causeCode === undefined ? {} : { causeCode: error.causeCode }),
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
const CANCELLATION_V1 = Symbol("lsp-capability-cancelled");
|
|
125
|
+
function runWithCancellation(run, signal) {
|
|
126
|
+
if (signal === undefined)
|
|
127
|
+
return run();
|
|
128
|
+
let onAbort;
|
|
129
|
+
const cancellation = new Promise((resolve) => {
|
|
130
|
+
onAbort = () => resolve(CANCELLATION_V1);
|
|
131
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
132
|
+
});
|
|
133
|
+
const pending = run();
|
|
134
|
+
return Promise.race([pending, cancellation]).finally(() => signal.removeEventListener("abort", onAbort));
|
|
135
|
+
}
|
|
136
|
+
/** Registers read-only language diagnostics using only host-owned refs and a host client. */
|
|
137
|
+
export function createLspDiagnosticsCapabilityFactoryV1(runtime) {
|
|
138
|
+
return (api) => {
|
|
139
|
+
api.registerTool({
|
|
140
|
+
name: "lsp_diagnostics",
|
|
141
|
+
label: "LSP diagnostics",
|
|
142
|
+
description: "Read bounded diagnostics from a host-bound language server",
|
|
143
|
+
parameters: Type.Object({ request: requestSchema }, { additionalProperties: false }),
|
|
144
|
+
execute: async (first, second, third) => {
|
|
145
|
+
let invocationResult;
|
|
146
|
+
try {
|
|
147
|
+
invocationResult = invocation(first, second);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return adapterFailed("profile-runtime-tool-call");
|
|
151
|
+
}
|
|
152
|
+
const { input, operationId } = invocationResult;
|
|
153
|
+
const signal = signalFrom(third);
|
|
154
|
+
if (signal?.aborted)
|
|
155
|
+
return cancelled(operationId);
|
|
156
|
+
let requestInput;
|
|
157
|
+
try {
|
|
158
|
+
requestInput = objectInput(input.request, undefined);
|
|
159
|
+
}
|
|
160
|
+
catch {
|
|
161
|
+
return adapterFailed(operationId);
|
|
162
|
+
}
|
|
163
|
+
try {
|
|
164
|
+
const output = await runWithCancellation(() => runtime.run({
|
|
165
|
+
version: 1,
|
|
166
|
+
operationId,
|
|
167
|
+
workspaceRef: requestInput.workspaceRef,
|
|
168
|
+
language: requestInput.language,
|
|
169
|
+
documentRef: requestInput.documentRef,
|
|
170
|
+
...(requestInput.diagnosticsLimit === undefined
|
|
171
|
+
? {}
|
|
172
|
+
: { diagnosticsLimit: requestInput.diagnosticsLimit }),
|
|
173
|
+
}, signal), signal);
|
|
174
|
+
if (output === CANCELLATION_V1)
|
|
175
|
+
return cancelled(operationId);
|
|
176
|
+
return boundedResult(output, operationId);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
return adapterFailed(operationId);
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=capability.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capability.js","sourceRoot":"","sources":["../src/capability.ts"],"names":[],"mappings":"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","sourcesContent":["import type { CapabilityPluginSyncFactory } from \"@agent-forge/plugin-sdk\";\nimport { Type } from \"typebox\";\nimport {\n\tLSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1,\n\tprojectLspDiagnosticsResultV1,\n\tsanitizeLspDiagnosticsTextV1,\n} from \"./node-adapter.ts\";\nimport type { LspDiagnosticsAdapterV1, LspDiagnosticsResultV1 } from \"./types.ts\";\n\nconst requestSchema = Type.Object(\n\t{\n\t\tworkspaceRef: Type.String({ minLength: 1, maxLength: 256, pattern: \"^[A-Za-z0-9][A-Za-z0-9._:-]*$\" }),\n\t\tlanguage: Type.String({ minLength: 1, maxLength: 64, pattern: \"^[A-Za-z][A-Za-z0-9_.+-]*$\" }),\n\t\tdocumentRef: Type.String({ minLength: 1, maxLength: 256, pattern: \"^[A-Za-z0-9][A-Za-z0-9._:-]*$\" }),\n\t\tdocumentPath: Type.Optional(\n\t\t\tType.String({ minLength: 1, maxLength: 512, pattern: \"^[A-Za-z0-9_][A-Za-z0-9._/-]*$\" }),\n\t\t),\n\t\tdiagnosticsLimit: Type.Optional(Type.Integer({ minimum: 1, maximum: 128 })),\n\t},\n\t{ additionalProperties: false },\n);\n\nfunction objectInput(first: unknown, second: unknown): Record<string, unknown> {\n\tconst value = second === undefined ? first : second;\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value))\n\t\tthrow new Error(\"LSP input must be an object\");\n\treturn value as Record<string, unknown>;\n}\n\nfunction invocation(\n\tfirst: unknown,\n\tsecond: unknown,\n): { readonly input: Record<string, unknown>; readonly operationId: string } {\n\tconst candidate = typeof first === \"string\" && first.trim() !== \"\" ? first : \"profile-runtime-tool-call\";\n\treturn {\n\t\tinput: objectInput(first, second),\n\t\toperationId:\n\t\t\tcandidate.length <= LSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1 ? candidate : \"profile-runtime-tool-call\",\n\t};\n}\n\nfunction signalFrom(value: unknown): AbortSignal | undefined {\n\treturn value instanceof AbortSignal ? value : undefined;\n}\n\nfunction emptySummary(): LspDiagnosticsResultV1[\"summary\"] {\n\treturn { total: 0, error: 0, warning: 0, info: 0, hint: 0 };\n}\n\nfunction cancelled(operationId: string): LspDiagnosticsResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"cancelled\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"cancelled\", message: \"LSP diagnostics were cancelled\" },\n\t};\n}\n\nfunction adapterFailed(operationId: string): LspDiagnosticsResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"adapter_failed\", message: \"LSP adapter returned an invalid result\" },\n\t};\n}\n\nfunction validSummary(value: unknown): boolean {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst summary = value as Record<string, unknown>;\n\tconst keys = [\"total\", \"error\", \"warning\", \"info\", \"hint\"];\n\tif (Object.keys(summary).some((key) => !keys.includes(key)) || keys.some((key) => !(key in summary))) return false;\n\tconst counts = keys.map((key) => summary[key]);\n\tif (counts.some((count) => !Number.isSafeInteger(count) || (count as number) < 0 || (count as number) > 10_000_000))\n\t\treturn false;\n\treturn (counts[1] as number) + (counts[2] as number) + (counts[3] as number) + (counts[4] as number) === counts[0];\n}\n\nfunction validError(value: unknown): boolean {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return false;\n\tconst error = value as Record<string, unknown>;\n\tconst keys = [\"version\", \"code\", \"message\", \"causeCode\"];\n\tif (Object.keys(error).some((key) => !keys.includes(key))) return false;\n\tif (error.version !== 1 || typeof error.code !== \"string\" || !/^[a-z][a-z0-9_]{0,127}$/u.test(error.code))\n\t\treturn false;\n\tif (typeof error.message !== \"string\" || error.message.trim() === \"\" || error.message.length > 512) return false;\n\treturn (\n\t\terror.causeCode === undefined ||\n\t\t(typeof error.causeCode === \"string\" && /^[a-z][a-z0-9_]{0,127}$/u.test(error.causeCode))\n\t);\n}\n\nfunction validReportArtifactRef(value: unknown): value is string {\n\treturn value === undefined || (typeof value === \"string\" && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u.test(value));\n}\n\nfunction boundedResult(value: unknown, operationId: string): LspDiagnosticsResultV1 {\n\tif (value === null || typeof value !== \"object\" || Array.isArray(value)) return adapterFailed(operationId);\n\tconst result = value as Record<string, unknown>;\n\tif (result.status === \"succeeded\")\n\t\treturn projectLspDiagnosticsResultV1(result, operationId) ?? adapterFailed(operationId);\n\tif (\n\t\tresult.version !== 1 ||\n\t\tresult.operationId !== operationId ||\n\t\t![\"failed\", \"cancelled\", \"needs_user\", \"unsupported\"].includes(result.status as string) ||\n\t\t!Array.isArray(result.diagnostics) ||\n\t\tresult.diagnostics.length !== 0 ||\n\t\tresult.diagnosticsTruncated !== false ||\n\t\tresult.error === undefined\n\t)\n\t\treturn adapterFailed(operationId);\n\tif (\n\t\tObject.keys(result).some(\n\t\t\t(key) =>\n\t\t\t\t![\n\t\t\t\t\t\"version\",\n\t\t\t\t\t\"operationId\",\n\t\t\t\t\t\"status\",\n\t\t\t\t\t\"summary\",\n\t\t\t\t\t\"diagnostics\",\n\t\t\t\t\t\"diagnosticsTruncated\",\n\t\t\t\t\t\"reportArtifactRef\",\n\t\t\t\t\t\"error\",\n\t\t\t\t].includes(key),\n\t\t)\n\t)\n\t\treturn adapterFailed(operationId);\n\tif (!validSummary(result.summary) || !validError(result.error) || !validReportArtifactRef(result.reportArtifactRef))\n\t\treturn adapterFailed(operationId);\n\tconst error = result.error as {\n\t\treadonly version: 1;\n\t\treadonly code: string;\n\t\treadonly message: string;\n\t\treadonly causeCode?: string;\n\t};\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: result.status as LspDiagnosticsResultV1[\"status\"],\n\t\tsummary: result.summary as LspDiagnosticsResultV1[\"summary\"],\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\t...(result.reportArtifactRef === undefined ? {} : { reportArtifactRef: result.reportArtifactRef }),\n\t\terror: {\n\t\t\tversion: 1,\n\t\t\tcode: error.code,\n\t\t\tmessage: sanitizeLspDiagnosticsTextV1(error.message),\n\t\t\t...(error.causeCode === undefined ? {} : { causeCode: error.causeCode }),\n\t\t},\n\t};\n}\n\nconst CANCELLATION_V1 = Symbol(\"lsp-capability-cancelled\");\n\nfunction runWithCancellation(\n\trun: () => Promise<LspDiagnosticsResultV1>,\n\tsignal: AbortSignal | undefined,\n): Promise<LspDiagnosticsResultV1 | typeof CANCELLATION_V1> {\n\tif (signal === undefined) return run();\n\tlet onAbort!: () => void;\n\tconst cancellation = new Promise<typeof CANCELLATION_V1>((resolve) => {\n\t\tonAbort = () => resolve(CANCELLATION_V1);\n\t\tsignal.addEventListener(\"abort\", onAbort, { once: true });\n\t});\n\tconst pending = run();\n\treturn Promise.race([pending, cancellation]).finally(() => signal.removeEventListener(\"abort\", onAbort));\n}\n\n/** Registers read-only language diagnostics using only host-owned refs and a host client. */\nexport function createLspDiagnosticsCapabilityFactoryV1(runtime: LspDiagnosticsAdapterV1): CapabilityPluginSyncFactory {\n\treturn (api) => {\n\t\tapi.registerTool({\n\t\t\tname: \"lsp_diagnostics\",\n\t\t\tlabel: \"LSP diagnostics\",\n\t\t\tdescription: \"Read bounded diagnostics from a host-bound language server\",\n\t\t\tparameters: Type.Object({ request: requestSchema }, { additionalProperties: false }),\n\t\t\texecute: async (first: unknown, second?: unknown, third?: unknown) => {\n\t\t\t\tlet invocationResult: { readonly input: Record<string, unknown>; readonly operationId: string };\n\t\t\t\ttry {\n\t\t\t\t\tinvocationResult = invocation(first, second);\n\t\t\t\t} catch {\n\t\t\t\t\treturn adapterFailed(\"profile-runtime-tool-call\");\n\t\t\t\t}\n\t\t\t\tconst { input, operationId } = invocationResult;\n\t\t\t\tconst signal = signalFrom(third);\n\t\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\t\tlet requestInput: Record<string, unknown>;\n\t\t\t\ttry {\n\t\t\t\t\trequestInput = objectInput(input.request, undefined);\n\t\t\t\t} catch {\n\t\t\t\t\treturn adapterFailed(operationId);\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\tconst output = await runWithCancellation(\n\t\t\t\t\t\t() =>\n\t\t\t\t\t\t\truntime.run(\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tversion: 1,\n\t\t\t\t\t\t\t\t\toperationId,\n\t\t\t\t\t\t\t\t\tworkspaceRef: requestInput.workspaceRef as string,\n\t\t\t\t\t\t\t\t\tlanguage: requestInput.language as string,\n\t\t\t\t\t\t\t\t\tdocumentRef: requestInput.documentRef as string,\n\t\t\t\t\t\t\t\t\t...(requestInput.diagnosticsLimit === undefined\n\t\t\t\t\t\t\t\t\t\t? {}\n\t\t\t\t\t\t\t\t\t\t: { diagnosticsLimit: requestInput.diagnosticsLimit as number }),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tsignal,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\tsignal,\n\t\t\t\t\t);\n\t\t\t\t\tif (output === CANCELLATION_V1) return cancelled(operationId);\n\t\t\t\t\treturn boundedResult(output, operationId);\n\t\t\t\t} catch {\n\t\t\t\t\treturn adapterFailed(operationId);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t};\n}\n"]}
|
package/dist/entry.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
|
|
3
|
+
*
|
|
4
|
+
* The adapter is deliberately constructed without host injection: language
|
|
5
|
+
* servers are resolved per workspace by the host, so the registered
|
|
6
|
+
* `lsp_diagnostics` tool reports an explicit `unsupported` result until a host
|
|
7
|
+
* binds a resolver and client through its own profile machinery (fail-closed
|
|
8
|
+
* by design, same contract as the embedded build).
|
|
9
|
+
*/
|
|
10
|
+
declare const _default: import("@agent-forge/plugin-sdk").CapabilityPluginSyncFactory;
|
|
11
|
+
export default _default;
|
|
12
|
+
//# sourceMappingURL=entry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.d.ts","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAGA;;;;;;;;GAQG;;AACH,wBAAiF","sourcesContent":["import { createLspDiagnosticsCapabilityFactoryV1 } from \"./capability.ts\";\nimport { createNodeLspAdapterV1 } from \"./node-adapter.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The adapter is deliberately constructed without host injection: language\n * servers are resolved per workspace by the host, so the registered\n * `lsp_diagnostics` tool reports an explicit `unsupported` result until a host\n * binds a resolver and client through its own profile machinery (fail-closed\n * by design, same contract as the embedded build).\n */\nexport default createLspDiagnosticsCapabilityFactoryV1(createNodeLspAdapterV1());\n"]}
|
package/dist/entry.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { createLspDiagnosticsCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
import { createNodeLspAdapterV1 } from "./node-adapter.js";
|
|
3
|
+
/**
|
|
4
|
+
* Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.
|
|
5
|
+
*
|
|
6
|
+
* The adapter is deliberately constructed without host injection: language
|
|
7
|
+
* servers are resolved per workspace by the host, so the registered
|
|
8
|
+
* `lsp_diagnostics` tool reports an explicit `unsupported` result until a host
|
|
9
|
+
* binds a resolver and client through its own profile machinery (fail-closed
|
|
10
|
+
* by design, same contract as the embedded build).
|
|
11
|
+
*/
|
|
12
|
+
export default createLspDiagnosticsCapabilityFactoryV1(createNodeLspAdapterV1());
|
|
13
|
+
//# sourceMappingURL=entry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../src/entry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uCAAuC,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;;;;;;;GAQG;AACH,eAAe,uCAAuC,CAAC,sBAAsB,EAAE,CAAC,CAAC","sourcesContent":["import { createLspDiagnosticsCapabilityFactoryV1 } from \"./capability.ts\";\nimport { createNodeLspAdapterV1 } from \"./node-adapter.ts\";\n\n/**\n * Default-export factory consumed by the host's `loadCapabilityPluginFromManifest`.\n *\n * The adapter is deliberately constructed without host injection: language\n * servers are resolved per workspace by the host, so the registered\n * `lsp_diagnostics` tool reports an explicit `unsupported` result until a host\n * binds a resolver and client through its own profile machinery (fail-closed\n * by design, same contract as the embedded build).\n */\nexport default createLspDiagnosticsCapabilityFactoryV1(createNodeLspAdapterV1());\n"]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createLspDiagnosticsCapabilityFactoryV1 } from "./capability.ts";
|
|
2
|
+
export { createDeterministicLspAdapterV1, createNodeLspAdapterV1, LSP_DIAGNOSTICS_MAX_MESSAGE_CHARS_V1, LSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1, LSP_DIAGNOSTICS_MAX_OUTPUT_BYTES_V1, projectLspDiagnosticsResultV1, sanitizeLspDiagnosticsTextV1, } from "./node-adapter.ts";
|
|
3
|
+
export { createNodeLspRealHostV1, type LspServerMapV1, type LspServerSpecV1, resolveLspServersV1, } from "./node-lsp-real-host.ts";
|
|
4
|
+
export { sanitizeBinaryOutput } from "./shell-utils.ts";
|
|
5
|
+
export type * from "./types.ts";
|
|
6
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uCAAuC,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EACN,+BAA+B,EAC/B,sBAAsB,EACtB,oCAAoC,EACpC,yCAAyC,EACzC,mCAAmC,EACnC,6BAA6B,EAC7B,4BAA4B,GAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,uBAAuB,EACvB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,mBAAmB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,mBAAmB,YAAY,CAAC","sourcesContent":["export { createLspDiagnosticsCapabilityFactoryV1 } from \"./capability.ts\";\nexport {\n\tcreateDeterministicLspAdapterV1,\n\tcreateNodeLspAdapterV1,\n\tLSP_DIAGNOSTICS_MAX_MESSAGE_CHARS_V1,\n\tLSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1,\n\tLSP_DIAGNOSTICS_MAX_OUTPUT_BYTES_V1,\n\tprojectLspDiagnosticsResultV1,\n\tsanitizeLspDiagnosticsTextV1,\n} from \"./node-adapter.ts\";\nexport {\n\tcreateNodeLspRealHostV1,\n\ttype LspServerMapV1,\n\ttype LspServerSpecV1,\n\tresolveLspServersV1,\n} from \"./node-lsp-real-host.ts\";\nexport { sanitizeBinaryOutput } from \"./shell-utils.ts\";\nexport type * from \"./types.ts\";\n"]}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { createLspDiagnosticsCapabilityFactoryV1 } from "./capability.js";
|
|
2
|
+
export { createDeterministicLspAdapterV1, createNodeLspAdapterV1, LSP_DIAGNOSTICS_MAX_MESSAGE_CHARS_V1, LSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1, LSP_DIAGNOSTICS_MAX_OUTPUT_BYTES_V1, projectLspDiagnosticsResultV1, sanitizeLspDiagnosticsTextV1, } from "./node-adapter.js";
|
|
3
|
+
export { createNodeLspRealHostV1, resolveLspServersV1, } from "./node-lsp-real-host.js";
|
|
4
|
+
export { sanitizeBinaryOutput } from "./shell-utils.js";
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uCAAuC,EAAE,MAAM,iBAAiB,CAAC;AAC1E,OAAO,EACN,+BAA+B,EAC/B,sBAAsB,EACtB,oCAAoC,EACpC,yCAAyC,EACzC,mCAAmC,EACnC,6BAA6B,EAC7B,4BAA4B,GAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACN,uBAAuB,EAGvB,mBAAmB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC","sourcesContent":["export { createLspDiagnosticsCapabilityFactoryV1 } from \"./capability.ts\";\nexport {\n\tcreateDeterministicLspAdapterV1,\n\tcreateNodeLspAdapterV1,\n\tLSP_DIAGNOSTICS_MAX_MESSAGE_CHARS_V1,\n\tLSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1,\n\tLSP_DIAGNOSTICS_MAX_OUTPUT_BYTES_V1,\n\tprojectLspDiagnosticsResultV1,\n\tsanitizeLspDiagnosticsTextV1,\n} from \"./node-adapter.ts\";\nexport {\n\tcreateNodeLspRealHostV1,\n\ttype LspServerMapV1,\n\ttype LspServerSpecV1,\n\tresolveLspServersV1,\n} from \"./node-lsp-real-host.ts\";\nexport { sanitizeBinaryOutput } from \"./shell-utils.ts\";\nexport type * from \"./types.ts\";\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type LspDeterministicFixtureV1, type LspDiagnosticsAdapterV1, type LspDiagnosticsResultV1, type NodeLspAdapterOptionsV1 } from "./types.ts";
|
|
2
|
+
export declare const LSP_DIAGNOSTICS_MAX_MESSAGE_CHARS_V1 = 512;
|
|
3
|
+
export declare const LSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1 = 256;
|
|
4
|
+
export declare const LSP_DIAGNOSTICS_MAX_OUTPUT_BYTES_V1: number;
|
|
5
|
+
/** Sanitizes host error/diagnostic text before the capability boundary. */
|
|
6
|
+
export declare function sanitizeLspDiagnosticsTextV1(value: string, maximum?: number): string;
|
|
7
|
+
/** Projects arbitrary host output into a bounded, path-safe model DTO. */
|
|
8
|
+
export declare function projectLspDiagnosticsResultV1(value: unknown, operationId: string): LspDiagnosticsResultV1 | undefined;
|
|
9
|
+
/** Creates a host-bound Node adapter. No command, cwd, environment, or source-text input is accepted. */
|
|
10
|
+
export declare function createNodeLspAdapterV1(options?: NodeLspAdapterOptionsV1): LspDiagnosticsAdapterV1;
|
|
11
|
+
/** Deterministic fixture adapter for tests; it never starts a language server. */
|
|
12
|
+
export declare function createDeterministicLspAdapterV1(fixtures: readonly LspDeterministicFixtureV1[]): LspDiagnosticsAdapterV1;
|
|
13
|
+
//# sourceMappingURL=node-adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-adapter.d.ts","sourceRoot":"","sources":["../src/node-adapter.ts"],"names":[],"mappings":"AAGA,OAAO,EAIN,KAAK,yBAAyB,EAI9B,KAAK,uBAAuB,EAE5B,KAAK,sBAAsB,EAG3B,KAAK,uBAAuB,EAC5B,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,oCAAoC,MAAM,CAAC;AACxD,eAAO,MAAM,yCAAyC,MAAM,CAAC;AAC7D,eAAO,MAAM,mCAAmC,QAAY,CAAC;AAuC7D,2EAA2E;AAC3E,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,SAAuC,GAAG,MAAM,CAElH;AA8ND,0EAA0E;AAC1E,wBAAgB,6BAA6B,CAAC,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,GAAG,sBAAsB,GAAG,SAAS,CA2CrH;AAkFD,yGAAyG;AACzG,wBAAgB,sBAAsB,CAAC,OAAO,GAAE,uBAA4B,GAAG,uBAAuB,CAyCrG;AAED,kFAAkF;AAClF,wBAAgB,+BAA+B,CAAC,QAAQ,EAAE,SAAS,yBAAyB,EAAE,2BAyB7F","sourcesContent":["import type { JsonValue } from \"@agent-forge/plugin-sdk\";\nimport { projectPluginContextV1 } from \"@agent-forge/plugin-sdk\";\nimport { sanitizeBinaryOutput } from \"./shell-utils.ts\";\nimport {\n\tLSP_DIAGNOSTICS_HOST_ADAPTER_ID_V1,\n\tLSP_DIAGNOSTICS_HOST_ADAPTER_VERSION_V1,\n\tLSP_DIAGNOSTICS_MAX_ITEMS_V1,\n\ttype LspDeterministicFixtureV1,\n\ttype LspDiagnosticPositionV1,\n\ttype LspDiagnosticRangeV1,\n\ttype LspDiagnosticSeverityV1,\n\ttype LspDiagnosticsAdapterV1,\n\ttype LspDiagnosticsRequestV1,\n\ttype LspDiagnosticsResultV1,\n\ttype LspDiagnosticV1,\n\ttype LspSessionV1,\n\ttype NodeLspAdapterOptionsV1,\n} from \"./types.ts\";\n\nexport const LSP_DIAGNOSTICS_MAX_MESSAGE_CHARS_V1 = 512;\nexport const LSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1 = 256;\nexport const LSP_DIAGNOSTICS_MAX_OUTPUT_BYTES_V1 = 64 * 1024;\n\nconst DEFAULT_TIMEOUT_MS_V1 = 30_000;\nconst MAX_TIMEOUT_MS_V1 = 120_000;\nconst MAX_HOST_DIAGNOSTICS_V1 = 4096;\nconst MAX_FILE_CHARS_V1 = 512;\nconst MAX_SOURCE_CHARS_V1 = 128;\nconst MAX_CODE_CHARS_V1 = 128;\nconst MAX_REF_CHARS_V1 = 256;\nconst MAX_LANGUAGE_CHARS_V1 = 64;\nconst MAX_POSITION_V1 = 10_000_000;\n\nconst OPAQUE_REF_V1 = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/u;\nconst LANGUAGE_V1 = /^[A-Za-z][A-Za-z0-9_.+-]{0,63}$/u;\nconst RELATIVE_FILE_V1 = /^[A-Za-z0-9_][A-Za-z0-9._/-]{0,511}$/u;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction hasOnlyKeys(value: Record<string, unknown>, allowed: readonly string[]): boolean {\n\treturn Object.keys(value).every((key) => allowed.includes(key));\n}\n\nfunction safeOpaqueRef(value: string): boolean {\n\treturn value.length <= MAX_REF_CHARS_V1 && OPAQUE_REF_V1.test(value);\n}\n\nfunction sanitizeLspText(value: string, maximum: number): string {\n\tconst text = sanitizeBinaryOutput(value)\n\t\t.replace(\n\t\t\t/((?:api[-_ ]?key|access[-_ ]?token|auth(?:orization)?|bearer|password|passwd|secret|token)\\s*[:=]\\s*)([^\\s,;]+)/giu,\n\t\t\t\"$1[redacted-secret]\",\n\t\t)\n\t\t.replace(/(?:[A-Za-z]:[\\\\/]|\\\\\\\\|\\/(?:home|Users|private|tmp|var|workspace)\\/)[^\\s\"'<>]*/gu, \"[redacted-path]\");\n\tconst chars = [...text];\n\treturn chars.length <= maximum ? text : chars.slice(0, maximum).join(\"\");\n}\n\n/** Sanitizes host error/diagnostic text before the capability boundary. */\nexport function sanitizeLspDiagnosticsTextV1(value: string, maximum = LSP_DIAGNOSTICS_MAX_MESSAGE_CHARS_V1): string {\n\treturn sanitizeLspText(value, maximum);\n}\n\nfunction validPositiveInteger(value: unknown, maximum: number): value is number {\n\treturn Number.isSafeInteger(value) && (value as number) >= 1 && (value as number) <= maximum;\n}\n\nfunction validNonNegativeInteger(value: unknown, maximum: number): value is number {\n\treturn Number.isSafeInteger(value) && (value as number) >= 0 && (value as number) <= maximum;\n}\n\nfunction emptySummary(): LspDiagnosticsResultV1[\"summary\"] {\n\treturn { total: 0, error: 0, warning: 0, info: 0, hint: 0 };\n}\n\nfunction invalidInput(operationId: string, message: string): LspDiagnosticsResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"invalid_input\", message },\n\t};\n}\n\nfunction unsupported(operationId: string): LspDiagnosticsResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"unsupported\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"lsp_unavailable\", message: \"No language server is bound by this host\" },\n\t};\n}\n\nfunction cancelled(operationId: string): LspDiagnosticsResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"cancelled\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"cancelled\", message: \"LSP diagnostics were cancelled\" },\n\t};\n}\n\nfunction timedOut(operationId: string): LspDiagnosticsResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"timeout\", message: \"LSP diagnostics timed out\" },\n\t};\n}\n\nfunction adapterFailed(operationId: string): LspDiagnosticsResultV1 {\n\treturn {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"failed\",\n\t\tsummary: emptySummary(),\n\t\tdiagnostics: [],\n\t\tdiagnosticsTruncated: false,\n\t\terror: { version: 1, code: \"adapter_failed\", message: \"LSP adapter returned an invalid result\" },\n\t};\n}\n\nfunction validateRequest(request: LspDiagnosticsRequestV1): string | undefined {\n\tif (request.version !== 1) return \"version must be 1\";\n\tif (\n\t\ttypeof request.operationId !== \"string\" ||\n\t\trequest.operationId.trim() === \"\" ||\n\t\trequest.operationId.length > LSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1\n\t)\n\t\treturn \"operationId is invalid\";\n\tif (typeof request.workspaceRef !== \"string\" || !safeOpaqueRef(request.workspaceRef))\n\t\treturn \"workspaceRef must be a safe opaque host handle\";\n\tif (typeof request.documentRef !== \"string\" || !safeOpaqueRef(request.documentRef))\n\t\treturn \"documentRef must be a safe opaque host handle\";\n\tif (\n\t\trequest.documentPath !== undefined &&\n\t\t(typeof request.documentPath !== \"string\" ||\n\t\t\trequest.documentPath.length > MAX_FILE_CHARS_V1 ||\n\t\t\t!RELATIVE_FILE_V1.test(request.documentPath) ||\n\t\t\trequest.documentPath.split(\"/\").some((part) => part === \".\" || part === \"..\"))\n\t)\n\t\treturn \"documentPath must be a safe workspace-relative identifier\";\n\tif (\n\t\ttypeof request.language !== \"string\" ||\n\t\trequest.language.length > MAX_LANGUAGE_CHARS_V1 ||\n\t\t!LANGUAGE_V1.test(request.language)\n\t)\n\t\treturn \"language is invalid\";\n\tif (\n\t\trequest.diagnosticsLimit !== undefined &&\n\t\t!validPositiveInteger(request.diagnosticsLimit, LSP_DIAGNOSTICS_MAX_ITEMS_V1)\n\t)\n\t\treturn `diagnosticsLimit must be a positive integer at most ${LSP_DIAGNOSTICS_MAX_ITEMS_V1}`;\n\treturn undefined;\n}\n\nfunction projectPosition(value: unknown): LspDiagnosticPositionV1 | undefined {\n\tif (!isRecord(value) || !hasOnlyKeys(value, [\"line\", \"column\"])) return undefined;\n\tif (!validNonNegativeInteger(value.line, MAX_POSITION_V1) || !validNonNegativeInteger(value.column, MAX_POSITION_V1))\n\t\treturn undefined;\n\treturn { line: value.line, column: value.column };\n}\n\nfunction projectRange(value: unknown): LspDiagnosticRangeV1 | undefined {\n\tif (!isRecord(value) || !hasOnlyKeys(value, [\"start\", \"end\"])) return undefined;\n\tconst start = projectPosition(value.start);\n\tconst end = projectPosition(value.end);\n\tif (start === undefined || end === undefined) return undefined;\n\tif (end.line < start.line || (end.line === start.line && end.column < start.column)) return undefined;\n\treturn { start, end };\n}\n\nfunction projectDiagnostic(value: unknown): LspDiagnosticV1 | undefined {\n\tif (!isRecord(value) || !hasOnlyKeys(value, [\"file\", \"range\", \"severity\", \"code\", \"source\", \"message\"]))\n\t\treturn undefined;\n\tif (\n\t\ttypeof value.file !== \"string\" ||\n\t\tvalue.file.length > MAX_FILE_CHARS_V1 ||\n\t\t!RELATIVE_FILE_V1.test(value.file) ||\n\t\tvalue.file.split(\"/\").some((part) => part === \".\" || part === \"..\")\n\t)\n\t\treturn undefined;\n\tconst range = projectRange(value.range);\n\tif (range === undefined) return undefined;\n\tif (\n\t\tvalue.severity !== \"error\" &&\n\t\tvalue.severity !== \"warning\" &&\n\t\tvalue.severity !== \"info\" &&\n\t\tvalue.severity !== \"hint\"\n\t)\n\t\treturn undefined;\n\tif (typeof value.message !== \"string\" || value.message.trim() === \"\" || value.message.length > 8192)\n\t\treturn undefined;\n\tif (\n\t\tvalue.code !== undefined &&\n\t\t!(typeof value.code === \"string\" || (typeof value.code === \"number\" && Number.isSafeInteger(value.code)))\n\t)\n\t\treturn undefined;\n\tif (typeof value.code === \"string\" && (value.code.length > MAX_CODE_CHARS_V1 || /[\\u0000\\r\\n]/u.test(value.code)))\n\t\treturn undefined;\n\tif (value.source !== undefined && (typeof value.source !== \"string\" || value.source.length > MAX_SOURCE_CHARS_V1))\n\t\treturn undefined;\n\treturn {\n\t\tfile: value.file,\n\t\trange,\n\t\tseverity: value.severity as LspDiagnosticSeverityV1,\n\t\t...(value.code === undefined\n\t\t\t? {}\n\t\t\t: { code: typeof value.code === \"string\" ? sanitizeLspText(value.code, MAX_CODE_CHARS_V1) : value.code }),\n\t\t...(value.source === undefined ? {} : { source: sanitizeLspText(value.source, MAX_SOURCE_CHARS_V1) }),\n\t\tmessage: sanitizeLspText(value.message, LSP_DIAGNOSTICS_MAX_MESSAGE_CHARS_V1),\n\t};\n}\n\nfunction summaryFor(diagnostics: readonly LspDiagnosticV1[]): LspDiagnosticsResultV1[\"summary\"] {\n\tconst summary = { total: diagnostics.length, error: 0, warning: 0, info: 0, hint: 0 };\n\tfor (const diagnostic of diagnostics) summary[diagnostic.severity] += 1;\n\treturn summary;\n}\n\nfunction validSummary(value: unknown): value is LspDiagnosticsResultV1[\"summary\"] {\n\tif (!isRecord(value) || !hasOnlyKeys(value, [\"total\", \"error\", \"warning\", \"info\", \"hint\"])) return false;\n\tconst counts = [value.total, value.error, value.warning, value.info, value.hint];\n\tif (\n\t\tcounts.some(\n\t\t\t(count) =>\n\t\t\t\t!Number.isSafeInteger(count) || (count as number) < 0 || (count as number) > MAX_HOST_DIAGNOSTICS_V1,\n\t\t)\n\t)\n\t\treturn false;\n\treturn (\n\t\t(value.error as number) + (value.warning as number) + (value.info as number) + (value.hint as number) ===\n\t\tvalue.total\n\t);\n}\n\nfunction contextBoundResult(result: LspDiagnosticsResultV1): LspDiagnosticsResultV1 {\n\tfor (let count = result.diagnostics.length; count >= 0; count--) {\n\t\tconst diagnostics = result.diagnostics.slice(0, count);\n\t\tconst candidate: LspDiagnosticsResultV1 = {\n\t\t\t...result,\n\t\t\tdiagnostics,\n\t\t\tdiagnosticsTruncated: result.diagnosticsTruncated || count < result.diagnostics.length,\n\t\t};\n\t\tconst projection = projectPluginContextV1(\n\t\t\t{\n\t\t\t\tversion: 1,\n\t\t\t\toperationId: result.operationId,\n\t\t\t\titems: [\n\t\t\t\t\tcandidate.summary as unknown as JsonValue,\n\t\t\t\t\t...diagnostics.map((item) => ({ ...item }) as unknown as JsonValue),\n\t\t\t\t],\n\t\t\t\t...(candidate.reportArtifactRef === undefined ? {} : { outputArtifactRef: candidate.reportArtifactRef }),\n\t\t\t\ttruncated: candidate.diagnosticsTruncated,\n\t\t\t},\n\t\t\t{\n\t\t\t\tmaxOutputBytes: LSP_DIAGNOSTICS_MAX_OUTPUT_BYTES_V1,\n\t\t\t\tmaxInlineItems: LSP_DIAGNOSTICS_MAX_ITEMS_V1 + 1,\n\t\t\t\tmaxCellChars: 512,\n\t\t\t},\n\t\t);\n\t\tif (\n\t\t\tprojection.status === \"succeeded\" &&\n\t\t\tnew TextEncoder().encode(JSON.stringify(candidate)).byteLength <= LSP_DIAGNOSTICS_MAX_OUTPUT_BYTES_V1\n\t\t)\n\t\t\treturn candidate;\n\t}\n\treturn adapterFailed(result.operationId);\n}\n\n/** Projects arbitrary host output into a bounded, path-safe model DTO. */\nexport function projectLspDiagnosticsResultV1(value: unknown, operationId: string): LspDiagnosticsResultV1 | undefined {\n\tif (\n\t\t!isRecord(value) ||\n\t\t!hasOnlyKeys(value, [\n\t\t\t\"version\",\n\t\t\t\"operationId\",\n\t\t\t\"status\",\n\t\t\t\"summary\",\n\t\t\t\"diagnostics\",\n\t\t\t\"diagnosticsTruncated\",\n\t\t\t\"reportArtifactRef\",\n\t\t\t\"error\",\n\t\t])\n\t)\n\t\treturn undefined;\n\tif (\n\t\tvalue.version !== 1 ||\n\t\tvalue.operationId !== operationId ||\n\t\tvalue.status !== \"succeeded\" ||\n\t\t!validSummary(value.summary) ||\n\t\t!Array.isArray(value.diagnostics)\n\t)\n\t\treturn undefined;\n\tif (value.diagnostics.length > LSP_DIAGNOSTICS_MAX_ITEMS_V1 || typeof value.diagnosticsTruncated !== \"boolean\")\n\t\treturn undefined;\n\tconst diagnostics = value.diagnostics.map(projectDiagnostic);\n\tif (diagnostics.some((diagnostic) => diagnostic === undefined)) return undefined;\n\tif (\n\t\tvalue.reportArtifactRef !== undefined &&\n\t\t(typeof value.reportArtifactRef !== \"string\" || !safeOpaqueRef(value.reportArtifactRef))\n\t)\n\t\treturn undefined;\n\tif (value.error !== undefined) return undefined;\n\tconst projected: LspDiagnosticsResultV1 = {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"succeeded\",\n\t\tsummary: summaryFor(diagnostics as LspDiagnosticV1[]),\n\t\tdiagnostics: diagnostics as LspDiagnosticV1[],\n\t\tdiagnosticsTruncated: value.diagnosticsTruncated,\n\t\t...(value.reportArtifactRef === undefined ? {} : { reportArtifactRef: value.reportArtifactRef }),\n\t};\n\treturn contextBoundResult(projected);\n}\n\nconst CANCELLATION_V1 = Symbol(\"lsp-cancelled\");\nconst TIMEOUT_V1 = Symbol(\"lsp-timeout\");\n\nasync function runBounded<T>(\n\trun: (signal: AbortSignal) => Promise<T>,\n\ttimeoutMs: number,\n\tsignal: AbortSignal | undefined,\n): Promise<T | typeof CANCELLATION_V1 | typeof TIMEOUT_V1 | undefined> {\n\tconst controller = new AbortController();\n\tlet stop: typeof CANCELLATION_V1 | typeof TIMEOUT_V1 | undefined;\n\tconst abort = () => {\n\t\tstop ??= CANCELLATION_V1;\n\t\tcontroller.abort();\n\t};\n\tif (signal?.aborted) return CANCELLATION_V1;\n\tsignal?.addEventListener(\"abort\", abort, { once: true });\n\tconst timeout = setTimeout(() => {\n\t\tstop ??= TIMEOUT_V1;\n\t\tcontroller.abort();\n\t}, timeoutMs);\n\tconst completed = Promise.resolve()\n\t\t.then(() => run(controller.signal))\n\t\t.then(\n\t\t\t(value) => value,\n\t\t\t() => undefined,\n\t\t);\n\tconst stopped = new Promise<typeof CANCELLATION_V1 | typeof TIMEOUT_V1>((resolve) => {\n\t\tcontroller.signal.addEventListener(\n\t\t\t\"abort\",\n\t\t\t() => {\n\t\t\t\tif (stop !== undefined) resolve(stop);\n\t\t\t},\n\t\t\t{ once: true },\n\t\t);\n\t});\n\ttry {\n\t\treturn await Promise.race([completed, stopped]);\n\t} finally {\n\t\tclearTimeout(timeout);\n\t\tsignal?.removeEventListener(\"abort\", abort);\n\t}\n}\n\nfunction validSession(value: unknown): value is LspSessionV1 {\n\treturn (\n\t\tisRecord(value) &&\n\t\thasOnlyKeys(value, [\"sessionRef\"]) &&\n\t\ttypeof value.sessionRef === \"string\" &&\n\t\tsafeOpaqueRef(value.sessionRef)\n\t);\n}\n\nfunction completeResult(operationId: string, value: unknown, limit: number): LspDiagnosticsResultV1 {\n\tif (!isRecord(value) || !hasOnlyKeys(value, [\"status\", \"diagnostics\", \"reportArtifactRef\"]))\n\t\treturn adapterFailed(operationId);\n\tif (value.status === \"unsupported\") return unsupported(operationId);\n\tif (value.status !== \"completed\" || (value.diagnostics !== undefined && !Array.isArray(value.diagnostics)))\n\t\treturn adapterFailed(operationId);\n\tconst rawDiagnostics = value.diagnostics ?? [];\n\tif (rawDiagnostics.length > MAX_HOST_DIAGNOSTICS_V1) return adapterFailed(operationId);\n\tconst projected = rawDiagnostics.map(projectDiagnostic);\n\tif (projected.some((diagnostic) => diagnostic === undefined)) return adapterFailed(operationId);\n\tconst bounded = (projected as LspDiagnosticV1[]).slice(0, Math.min(limit, LSP_DIAGNOSTICS_MAX_ITEMS_V1));\n\tconst result: LspDiagnosticsResultV1 = {\n\t\tversion: 1,\n\t\toperationId,\n\t\tstatus: \"succeeded\",\n\t\tsummary: summaryFor(projected as LspDiagnosticV1[]),\n\t\tdiagnostics: bounded,\n\t\tdiagnosticsTruncated: rawDiagnostics.length > bounded.length,\n\t\t...(value.reportArtifactRef === undefined ? {} : { reportArtifactRef: value.reportArtifactRef as string }),\n\t};\n\tif (\n\t\tvalue.reportArtifactRef !== undefined &&\n\t\t(typeof value.reportArtifactRef !== \"string\" || !safeOpaqueRef(value.reportArtifactRef))\n\t)\n\t\treturn adapterFailed(operationId);\n\treturn contextBoundResult(result);\n}\n\n/** Creates a host-bound Node adapter. No command, cwd, environment, or source-text input is accepted. */\nexport function createNodeLspAdapterV1(options: NodeLspAdapterOptionsV1 = {}): LspDiagnosticsAdapterV1 {\n\tconst defaultTimeoutMs = options.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS_V1;\n\tif (!validPositiveInteger(defaultTimeoutMs, MAX_TIMEOUT_MS_V1))\n\t\tthrow new Error(`defaultTimeoutMs must be a positive integer at most ${MAX_TIMEOUT_MS_V1}`);\n\treturn {\n\t\tadapterId: LSP_DIAGNOSTICS_HOST_ADAPTER_ID_V1,\n\t\tadapterVersion: LSP_DIAGNOSTICS_HOST_ADAPTER_VERSION_V1,\n\t\tasync run(request, signal) {\n\t\t\tconst operationId =\n\t\t\t\ttypeof request.operationId === \"string\" && request.operationId.trim() !== \"\"\n\t\t\t\t\t? request.operationId.length <= LSP_DIAGNOSTICS_MAX_OPERATION_ID_CHARS_V1\n\t\t\t\t\t\t? request.operationId\n\t\t\t\t\t\t: \"profile-runtime-tool-call\"\n\t\t\t\t\t: \"invalid-lsp-operation\";\n\t\t\tconst invalid = validateRequest(request);\n\t\t\tif (invalid !== undefined) return invalidInput(operationId, invalid);\n\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\tif (options.resolver === undefined || options.client === undefined) return unsupported(operationId);\n\t\t\tconst resolution = await runBounded(\n\t\t\t\t(internalSignal) => options.resolver!.resolve(request, internalSignal).then((session) => ({ session })),\n\t\t\t\tdefaultTimeoutMs,\n\t\t\t\tsignal,\n\t\t\t);\n\t\t\tif (resolution === CANCELLATION_V1) return cancelled(operationId);\n\t\t\tif (resolution === TIMEOUT_V1) return timedOut(operationId);\n\t\t\tif (resolution === undefined) return adapterFailed(operationId);\n\t\t\tif (signal?.aborted) return cancelled(operationId);\n\t\t\tconst session = resolution.session;\n\t\t\tif (session === undefined) return unsupported(operationId);\n\t\t\tif (!validSession(session)) return adapterFailed(operationId);\n\t\t\tconst raw = await runBounded(\n\t\t\t\t(internalSignal) => options.client!.diagnose({ session, request }, internalSignal),\n\t\t\t\tdefaultTimeoutMs,\n\t\t\t\tsignal,\n\t\t\t);\n\t\t\tif (raw === CANCELLATION_V1) return cancelled(operationId);\n\t\t\tif (raw === TIMEOUT_V1) return timedOut(operationId);\n\t\t\tif (raw === undefined) return adapterFailed(operationId);\n\t\t\treturn completeResult(operationId, raw, request.diagnosticsLimit ?? LSP_DIAGNOSTICS_MAX_ITEMS_V1);\n\t\t},\n\t};\n}\n\n/** Deterministic fixture adapter for tests; it never starts a language server. */\nexport function createDeterministicLspAdapterV1(fixtures: readonly LspDeterministicFixtureV1[]) {\n\tconst fixtureByKey = new Map<string, LspDeterministicFixtureV1>();\n\tfor (const fixture of fixtures) {\n\t\tconst key = `${fixture.workspaceRef}\\u0000${fixture.language}\\u0000${fixture.documentRef}`;\n\t\tif (fixtureByKey.has(key)) throw new Error(\"Deterministic LSP fixtures must be unique\");\n\t\tfixtureByKey.set(key, fixture);\n\t}\n\treturn createNodeLspAdapterV1({\n\t\tresolver: {\n\t\t\tasync resolve(request) {\n\t\t\t\tconst fixture = fixtureByKey.get(\n\t\t\t\t\t`${request.workspaceRef}\\u0000${request.language}\\u0000${request.documentRef}`,\n\t\t\t\t);\n\t\t\t\treturn fixture === undefined ? undefined : { sessionRef: `fixture:${request.documentRef}` };\n\t\t\t},\n\t\t},\n\t\tclient: {\n\t\t\tasync diagnose(input) {\n\t\t\t\tconst fixture = fixtureByKey.get(\n\t\t\t\t\t`${input.request.workspaceRef}\\u0000${input.request.language}\\u0000${input.request.documentRef}`,\n\t\t\t\t);\n\t\t\t\treturn fixture?.result ?? { status: \"unsupported\" };\n\t\t\t},\n\t\t},\n\t});\n}\n"]}
|