nemo-fabric-adapters-pi 0.0.0 → 0.3.0-beta.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 +146 -7
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +18 -0
- package/dist/node-version.d.ts +1 -0
- package/dist/node-version.js +38 -0
- package/dist/pi-sdk.d.ts +8 -0
- package/dist/pi-sdk.js +466 -0
- package/dist/runtime.d.ts +26 -0
- package/dist/runtime.js +71 -0
- package/package.json +55 -2
- package/pi.fabric-adapter.json +70 -0
package/README.md
CHANGED
|
@@ -1,9 +1,148 @@
|
|
|
1
|
-
|
|
1
|
+
<!--
|
|
2
|
+
SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
3
|
+
SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
-->
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
`nemo-fabric-adapters-pi` package name. It contains no runtime code and is not a
|
|
5
|
-
supported NVIDIA NeMo Fabric adapter release.
|
|
6
|
+
# NVIDIA NeMo Fabric Pi Adapter
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
This package provides a Pi harness adapter for NVIDIA NeMo Fabric. It embeds the
|
|
9
|
+
Pi SDK in the Node process of the adapter and maps one NeMo Fabric runtime to one
|
|
10
|
+
in-memory Pi session.
|
|
11
|
+
|
|
12
|
+
The adapter supports:
|
|
13
|
+
|
|
14
|
+
- One explicit Pi-known model selected from the `default` role or the sole
|
|
15
|
+
configured role
|
|
16
|
+
- Runtime API-key credentials named by `models.<role>.api_key_env`
|
|
17
|
+
- Optional `models.<role>.base_url`
|
|
18
|
+
- Optional replacement system instructions
|
|
19
|
+
- Tool allow and block policy
|
|
20
|
+
- NeMo Fabric custom tools loaded through normalized `tools.definitions`
|
|
21
|
+
- Explicit normalized `skills.paths`
|
|
22
|
+
- Explicit local `.ts` or `.js` extension files contained by the NeMo Fabric
|
|
23
|
+
workspace
|
|
24
|
+
- Slash commands registered by those explicit extensions
|
|
25
|
+
- Ordered plain-text invocations with a `{ "response": "..." }` terminal
|
|
26
|
+
output
|
|
27
|
+
|
|
28
|
+
Ambient Pi settings, context files, packages, extensions, skills, prompts,
|
|
29
|
+
themes, model files, credentials, and session files are disabled. Explicitly
|
|
30
|
+
configured extensions are trusted code.
|
|
31
|
+
|
|
32
|
+
## Install the Adapter
|
|
33
|
+
|
|
34
|
+
Pi 0.84.x requires Node.js 22.19.0 or newer.
|
|
35
|
+
|
|
36
|
+
### Install for Consumers
|
|
37
|
+
|
|
38
|
+
Install the adapter in the project that owns the NeMo Fabric configuration,
|
|
39
|
+
then install the compatible Pi SDK harness version selected by that project:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install nemo-fabric-adapters-pi
|
|
43
|
+
npm install @earendil-works/pi-ai@^0.84.2 @earendil-works/pi-coding-agent@^0.84.2
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The adapter declares the Pi packages as optional peers. Installing the adapter
|
|
47
|
+
alone does not install a harness, and starting it without compatible Pi packages
|
|
48
|
+
returns `pi_harness_unavailable`.
|
|
49
|
+
|
|
50
|
+
### Install for Source Development
|
|
51
|
+
|
|
52
|
+
For focused Pi development in the NeMo Fabric source tree, install the Pi
|
|
53
|
+
adapter workspace and its pinned harness from the repository root:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
just install-typescript-pi
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
To install and build all maintained TypeScript workspaces instead, run:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
just build-typescript
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The full build installs its own dependencies, so you do not need to run
|
|
66
|
+
`just install-typescript-pi` first.
|
|
67
|
+
|
|
68
|
+
## Configure the Adapter
|
|
69
|
+
|
|
70
|
+
The npm package includes its adapter descriptor as `pi.fabric-adapter.json`.
|
|
71
|
+
NeMo Fabric descriptor discovery is path-based. Point discovery at the installed
|
|
72
|
+
descriptor and select the Pi adapter with the Python SDK:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from nemo_fabric import DiscoveryConfig, HarnessConfig
|
|
76
|
+
|
|
77
|
+
discovery = DiscoveryConfig(
|
|
78
|
+
local_paths=[
|
|
79
|
+
"./node_modules/nemo-fabric-adapters-pi/pi.fabric-adapter.json"
|
|
80
|
+
]
|
|
81
|
+
)
|
|
82
|
+
harness = HarnessConfig(adapter_id="nvidia.fabric.pi")
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
For a source build, set `discovery.local_paths` to
|
|
86
|
+
`adapters/typescript/pi/pi.fabric-adapter.json` instead.
|
|
87
|
+
|
|
88
|
+
## Custom Tool Modules
|
|
89
|
+
|
|
90
|
+
The adapter accepts custom tools with `kind: "module"`. The `ref` is a
|
|
91
|
+
workspace-relative JavaScript or TypeScript file with an optional named export,
|
|
92
|
+
for example `tools/review.ts#createTool`. Without a fragment, the adapter uses
|
|
93
|
+
the default export.
|
|
94
|
+
|
|
95
|
+
The export is called with `{ name, settings, workspace }` and must return a Pi
|
|
96
|
+
`ToolDefinition` whose name matches the normalized `tools.definitions` key.
|
|
97
|
+
Tool modules are trusted executable code. Their real paths must remain inside
|
|
98
|
+
the NeMo Fabric workspace, and their names may not replace Pi built-in or
|
|
99
|
+
extension tools. The following configuration registers a custom tool module:
|
|
100
|
+
|
|
101
|
+
```json
|
|
102
|
+
{
|
|
103
|
+
"tools": {
|
|
104
|
+
"definitions": {
|
|
105
|
+
"review_context": {
|
|
106
|
+
"kind": "module",
|
|
107
|
+
"ref": "tools/review-context.ts#createTool",
|
|
108
|
+
"settings": {"format": "brief"}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
"enabled": ["read", "review_context"]
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Run the Code-Review Example
|
|
117
|
+
|
|
118
|
+
The maintained code-review example exercises the Pi adapter with an explicit
|
|
119
|
+
NeMo Fabric skill and an example-specific tool policy. After building the
|
|
120
|
+
TypeScript packages, inspect the plan from the repository root:
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
.venv/bin/python -m examples.code_review_agent --variant pi --plan
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Refer to the
|
|
127
|
+
[code-review example](../../../examples/code_review_agent/README.md) for the
|
|
128
|
+
live NVIDIA-backed run command. Relay and MCP are not currently supported.
|
|
129
|
+
|
|
130
|
+
## Dependency Rationale
|
|
131
|
+
|
|
132
|
+
`@earendil-works/pi-coding-agent` provides the native Pi session, resources,
|
|
133
|
+
skills, extensions, and tools. Using the SDK keeps these integration points in
|
|
134
|
+
process; maintaining a second JSON-RPC translation was rejected for the bundled
|
|
135
|
+
adapter. `@earendil-works/pi-ai` supplies Pi's model catalog and credential
|
|
136
|
+
store, which the coding-agent SDK expects. Both packages are optional peer
|
|
137
|
+
dependencies so deployments control the compatible harness version. Exact
|
|
138
|
+
0.84.2 development dependencies keep repository builds and tests reproducible.
|
|
139
|
+
|
|
140
|
+
`jiti` loads explicitly configured, trusted JavaScript and TypeScript tool
|
|
141
|
+
modules. Native Node.js loading cannot execute TypeScript modules, while a
|
|
142
|
+
custom transpiler would duplicate this focused loader. The adapter contract
|
|
143
|
+
package supplies normalized types, and `nemo-fabric-adapters-common` supplies
|
|
144
|
+
the shared lifecycle host; copying either surface into the Pi package would
|
|
145
|
+
create divergent implementations.
|
|
146
|
+
|
|
147
|
+
`typescript` and `@types/node` are exact-pinned build inputs and are absent from
|
|
148
|
+
the published production dependency graph.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
3
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
// Process runner referenced by the adapter descriptor and npm binary. It
|
|
5
|
+
// validates the Node.js host, then composes the shared lifecycle transport with
|
|
6
|
+
// the Pi runtime while deferring SDK loading until startup.
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import { serve } from "nemo-fabric-adapters-common";
|
|
9
|
+
import { assertSupportedNodeVersion } from "./node-version.js";
|
|
10
|
+
const manifest = createRequire(import.meta.url)("../package.json");
|
|
11
|
+
assertSupportedNodeVersion(process.versions.node, manifest.engines?.node);
|
|
12
|
+
await serve(async () => {
|
|
13
|
+
const [{ PiSdkSessionFactory }, { PiAdapterRuntime }] = await Promise.all([
|
|
14
|
+
import("./pi-sdk.js"),
|
|
15
|
+
import("./runtime.js"),
|
|
16
|
+
]);
|
|
17
|
+
return new PiAdapterRuntime(new PiSdkSessionFactory());
|
|
18
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function assertSupportedNodeVersion(current: string, requirement: unknown): void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
function parseVersion(value, label) {
|
|
4
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
|
|
5
|
+
if (match === null) {
|
|
6
|
+
throw new Error(`${label} must be a semantic version`);
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
major: Number(match[1]),
|
|
10
|
+
minor: Number(match[2]),
|
|
11
|
+
patch: Number(match[3]),
|
|
12
|
+
prerelease: match[4] !== undefined,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function isOlderThan(actual, minimum) {
|
|
16
|
+
for (const part of ["major", "minor", "patch"]) {
|
|
17
|
+
const actualPart = actual[part];
|
|
18
|
+
const minimumPart = minimum[part];
|
|
19
|
+
if (actualPart !== minimumPart) {
|
|
20
|
+
return actualPart < minimumPart;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
export function assertSupportedNodeVersion(current, requirement) {
|
|
26
|
+
if (typeof requirement !== "string") {
|
|
27
|
+
throw new Error("The Pi package must declare a Node.js engine requirement");
|
|
28
|
+
}
|
|
29
|
+
const minimumMatch = /^>=(\d+\.\d+\.\d+)$/.exec(requirement);
|
|
30
|
+
if (minimumMatch?.[1] === undefined) {
|
|
31
|
+
throw new Error(`The Pi adapter cannot enforce Node.js engine requirement ${requirement}`);
|
|
32
|
+
}
|
|
33
|
+
const actual = parseVersion(current, "The current Node.js version");
|
|
34
|
+
const minimum = parseVersion(minimumMatch[1], "The minimum Node.js version");
|
|
35
|
+
if (actual.prerelease || isOlderThan(actual, minimum)) {
|
|
36
|
+
throw new Error(`The Pi adapter requires Node.js ${requirement}; current runtime is ${current}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
package/dist/pi-sdk.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ToolDefinition } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import type { AgentToolDefinition } from "nemo-fabric-adapter-contract";
|
|
3
|
+
import { type AdapterStartInput } from "nemo-fabric-adapters-common";
|
|
4
|
+
import type { PiSessionFactory, PiSessionHandle } from "./runtime.js";
|
|
5
|
+
export declare function resolveCustomTools(workspace: string, definitions: Record<string, AgentToolDefinition>): Promise<ToolDefinition[]>;
|
|
6
|
+
export declare class PiSdkSessionFactory implements PiSessionFactory {
|
|
7
|
+
create(input: AdapterStartInput): Promise<PiSessionHandle>;
|
|
8
|
+
}
|
package/dist/pi-sdk.js
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
// Pi SDK integration boundary. It translates normalized adapter configuration
|
|
4
|
+
// into a controlled in-memory Pi session, including model credentials, skills,
|
|
5
|
+
// extensions, custom tools, and workspace containment.
|
|
6
|
+
import { realpath, stat } from "node:fs/promises";
|
|
7
|
+
import { extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
8
|
+
import { createJiti } from "jiti/static";
|
|
9
|
+
import { LifecycleError } from "nemo-fabric-adapters-common";
|
|
10
|
+
const PI_BUILTIN_TOOL_NAMES = new Set(["read", "bash", "edit", "write", "grep", "find", "ls"]);
|
|
11
|
+
const TOOL_MODULE_EXTENSIONS = new Set([".js", ".mjs", ".cjs", ".ts", ".mts", ".cts"]);
|
|
12
|
+
const PI_HARNESS_INSTALL_COMMAND = "npm install @earendil-works/pi-ai@^0.84.2 @earendil-works/pi-coding-agent@^0.84.2";
|
|
13
|
+
function isMissingModuleError(error) {
|
|
14
|
+
return (typeof error === "object" &&
|
|
15
|
+
error !== null &&
|
|
16
|
+
"code" in error &&
|
|
17
|
+
(error.code === "ERR_MODULE_NOT_FOUND" || error.code === "MODULE_NOT_FOUND"));
|
|
18
|
+
}
|
|
19
|
+
async function loadPiSdk() {
|
|
20
|
+
let ai;
|
|
21
|
+
let codingAgent;
|
|
22
|
+
try {
|
|
23
|
+
[ai, codingAgent] = await Promise.all([
|
|
24
|
+
import("@earendil-works/pi-ai"),
|
|
25
|
+
import("@earendil-works/pi-coding-agent"),
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
if (isMissingModuleError(error)) {
|
|
30
|
+
throw new LifecycleError("pi_harness_unavailable", `The Pi SDK harness is not installed. Install a compatible harness with: ${PI_HARNESS_INSTALL_COMMAND}`);
|
|
31
|
+
}
|
|
32
|
+
throw new LifecycleError("pi_harness_load_failed", "The installed Pi SDK harness could not be loaded");
|
|
33
|
+
}
|
|
34
|
+
if (typeof ai.InMemoryCredentialStore !== "function" ||
|
|
35
|
+
typeof codingAgent.createAgentSession !== "function" ||
|
|
36
|
+
typeof codingAgent.DefaultResourceLoader !== "function" ||
|
|
37
|
+
typeof codingAgent.ModelRuntime !== "function" ||
|
|
38
|
+
typeof codingAgent.SessionManager !== "function" ||
|
|
39
|
+
typeof codingAgent.SettingsManager !== "function") {
|
|
40
|
+
throw new LifecycleError("pi_harness_incompatible", "The installed Pi SDK harness does not expose the APIs required by this adapter");
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
InMemoryCredentialStore: ai.InMemoryCredentialStore,
|
|
44
|
+
createAgentSession: codingAgent.createAgentSession,
|
|
45
|
+
DefaultResourceLoader: codingAgent.DefaultResourceLoader,
|
|
46
|
+
ModelRuntime: codingAgent.ModelRuntime,
|
|
47
|
+
SessionManager: codingAgent.SessionManager,
|
|
48
|
+
SettingsManager: codingAgent.SettingsManager,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function selectModel(config) {
|
|
52
|
+
const entries = Object.entries(config.models ?? {});
|
|
53
|
+
if (entries.length === 0) {
|
|
54
|
+
throw new LifecycleError("pi_model_required", "The Pi adapter requires one configured model");
|
|
55
|
+
}
|
|
56
|
+
const selected = config.models?.default ?? (entries.length === 1 ? entries[0]?.[1] : undefined);
|
|
57
|
+
if (selected === undefined) {
|
|
58
|
+
throw new LifecycleError("pi_model_ambiguous", "Configure a default model role when the Pi adapter receives multiple models");
|
|
59
|
+
}
|
|
60
|
+
return selected;
|
|
61
|
+
}
|
|
62
|
+
function harnessSettings(config) {
|
|
63
|
+
const raw = config.harness?.settings;
|
|
64
|
+
const extensions = raw?.extensions;
|
|
65
|
+
if (extensions === undefined) {
|
|
66
|
+
return { extensions: [] };
|
|
67
|
+
}
|
|
68
|
+
if (!Array.isArray(extensions)) {
|
|
69
|
+
throw new LifecycleError("pi_invalid_settings", "Pi extension settings do not match the adapter schema");
|
|
70
|
+
}
|
|
71
|
+
const values = [];
|
|
72
|
+
for (const entry of extensions) {
|
|
73
|
+
if (typeof entry !== "string") {
|
|
74
|
+
throw new LifecycleError("pi_invalid_settings", "Pi extension settings do not match the adapter schema");
|
|
75
|
+
}
|
|
76
|
+
values.push(entry);
|
|
77
|
+
}
|
|
78
|
+
return { extensions: values };
|
|
79
|
+
}
|
|
80
|
+
function containedBy(root, candidate) {
|
|
81
|
+
const path = relative(root, candidate);
|
|
82
|
+
return path === "" || (!path.startsWith(`..${sep}`) && path !== ".." && !isAbsolute(path));
|
|
83
|
+
}
|
|
84
|
+
function isRecord(value) {
|
|
85
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
86
|
+
}
|
|
87
|
+
async function resolveToolModule(workspace, reference) {
|
|
88
|
+
const match = /^(?<path>[^#]+?)(?:#(?<export>[A-Za-z_$][\w$]*))?$/.exec(reference);
|
|
89
|
+
const configuredPath = match?.groups?.path;
|
|
90
|
+
if (configuredPath === undefined || isAbsolute(configuredPath)) {
|
|
91
|
+
throw new LifecycleError("pi_tool_ref_invalid", "Pi tool module references must use a workspace-relative path with an optional export fragment");
|
|
92
|
+
}
|
|
93
|
+
let candidate;
|
|
94
|
+
try {
|
|
95
|
+
candidate = await realpath(resolve(workspace, configuredPath));
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
throw new LifecycleError("pi_tool_module_not_found", "A configured Pi tool module does not exist");
|
|
99
|
+
}
|
|
100
|
+
if (!containedBy(workspace, candidate)) {
|
|
101
|
+
throw new LifecycleError("pi_tool_module_outside_workspace", "Pi tool modules must be contained by the workspace");
|
|
102
|
+
}
|
|
103
|
+
if (!(await stat(candidate)).isFile() || !TOOL_MODULE_EXTENSIONS.has(extname(candidate))) {
|
|
104
|
+
throw new LifecycleError("pi_tool_module_invalid", "Pi tool modules must be JavaScript or TypeScript files");
|
|
105
|
+
}
|
|
106
|
+
return { path: candidate, exportName: match?.groups?.export ?? "default" };
|
|
107
|
+
}
|
|
108
|
+
function validateToolDefinition(name, value) {
|
|
109
|
+
if (!isRecord(value) ||
|
|
110
|
+
value.name !== name ||
|
|
111
|
+
typeof value.label !== "string" ||
|
|
112
|
+
value.label.length === 0 ||
|
|
113
|
+
typeof value.description !== "string" ||
|
|
114
|
+
value.description.length === 0 ||
|
|
115
|
+
!isRecord(value.parameters) ||
|
|
116
|
+
typeof value.execute !== "function") {
|
|
117
|
+
throw new LifecycleError("pi_tool_factory_invalid", "A Pi tool factory returned an invalid tool definition or a mismatched tool name", { metadata: { tool: name } });
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
export async function resolveCustomTools(workspace, definitions) {
|
|
122
|
+
const jiti = createJiti(import.meta.url, { interopDefault: false });
|
|
123
|
+
const tools = [];
|
|
124
|
+
for (const [name, definition] of Object.entries(definitions)) {
|
|
125
|
+
if (PI_BUILTIN_TOOL_NAMES.has(name)) {
|
|
126
|
+
throw new LifecycleError("pi_tool_collision", "A Fabric tool definition collides with a Pi built-in tool", {
|
|
127
|
+
metadata: { tool: name },
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
if (definition.kind !== "module") {
|
|
131
|
+
throw new LifecycleError("pi_tool_kind_unsupported", "Pi supports only module tool definitions");
|
|
132
|
+
}
|
|
133
|
+
const moduleReference = await resolveToolModule(workspace, definition.ref);
|
|
134
|
+
let loaded;
|
|
135
|
+
try {
|
|
136
|
+
loaded = await jiti.import(moduleReference.path);
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
throw new LifecycleError("pi_tool_module_load_failed", "A configured Pi tool module could not be loaded", {
|
|
140
|
+
metadata: { tool: name },
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const factory = isRecord(loaded) ? loaded[moduleReference.exportName] : undefined;
|
|
144
|
+
if (typeof factory !== "function") {
|
|
145
|
+
throw new LifecycleError("pi_tool_factory_missing", "A configured Pi tool module export is not a factory", {
|
|
146
|
+
metadata: { tool: name },
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
let tool;
|
|
150
|
+
try {
|
|
151
|
+
tool = await factory({
|
|
152
|
+
name,
|
|
153
|
+
settings: definition.settings ?? {},
|
|
154
|
+
workspace,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
throw new LifecycleError("pi_tool_factory_failed", "A configured Pi tool factory failed", {
|
|
159
|
+
metadata: { tool: name },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
tools.push(validateToolDefinition(name, tool));
|
|
163
|
+
}
|
|
164
|
+
return tools;
|
|
165
|
+
}
|
|
166
|
+
function extensionToolNames(resourceLoader) {
|
|
167
|
+
return resourceLoader
|
|
168
|
+
.getExtensions()
|
|
169
|
+
.extensions.flatMap((extension) => Array.from(extension.tools.keys()));
|
|
170
|
+
}
|
|
171
|
+
function rejectToolCollisions(customTools, configuredExtensionTools) {
|
|
172
|
+
const customNames = new Set(customTools.map((tool) => tool.name));
|
|
173
|
+
const seenExtensionNames = new Set();
|
|
174
|
+
for (const name of configuredExtensionTools) {
|
|
175
|
+
if (PI_BUILTIN_TOOL_NAMES.has(name) || customNames.has(name) || seenExtensionNames.has(name)) {
|
|
176
|
+
throw new LifecycleError("pi_tool_collision", "Two configured Pi tool sources use the same tool name", {
|
|
177
|
+
metadata: { tool: name },
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
seenExtensionNames.add(name);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async function resolveExtensionPaths(workspace, configured) {
|
|
184
|
+
const resolved = [];
|
|
185
|
+
for (const entry of configured) {
|
|
186
|
+
if (isAbsolute(entry)) {
|
|
187
|
+
throw new LifecycleError("pi_extension_outside_workspace", "Pi extension paths must be workspace-relative");
|
|
188
|
+
}
|
|
189
|
+
let candidate;
|
|
190
|
+
try {
|
|
191
|
+
candidate = await realpath(resolve(workspace, entry));
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
throw new LifecycleError("pi_extension_not_found", "A configured Pi extension path does not exist");
|
|
195
|
+
}
|
|
196
|
+
if (!containedBy(workspace, candidate)) {
|
|
197
|
+
throw new LifecycleError("pi_extension_outside_workspace", "Pi extension path resolves outside the workspace");
|
|
198
|
+
}
|
|
199
|
+
const info = await stat(candidate);
|
|
200
|
+
if (!info.isFile() || (!candidate.endsWith(".ts") && !candidate.endsWith(".js"))) {
|
|
201
|
+
throw new LifecycleError("pi_unsupported_extension", "Pi extensions must be .ts or .js files");
|
|
202
|
+
}
|
|
203
|
+
resolved.push(candidate);
|
|
204
|
+
}
|
|
205
|
+
return resolved;
|
|
206
|
+
}
|
|
207
|
+
async function resolveSkillPaths(baseDir, configured) {
|
|
208
|
+
const resolved = [];
|
|
209
|
+
for (const entry of configured) {
|
|
210
|
+
let candidate;
|
|
211
|
+
try {
|
|
212
|
+
candidate = await realpath(resolve(baseDir, entry));
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
throw new LifecycleError("pi_skill_not_found", "A configured NeMo Fabric skill path does not exist");
|
|
216
|
+
}
|
|
217
|
+
let info;
|
|
218
|
+
try {
|
|
219
|
+
info = await stat(candidate);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
throw new LifecycleError("pi_skill_not_found", "A configured NeMo Fabric skill path does not exist");
|
|
223
|
+
}
|
|
224
|
+
if (!info.isDirectory()) {
|
|
225
|
+
throw new LifecycleError("pi_skill_invalid", "NeMo Fabric skill paths must be directories");
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
if (!(await stat(join(candidate, "SKILL.md"))).isFile()) {
|
|
229
|
+
throw new Error("not a file");
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
throw new LifecycleError("pi_skill_invalid", "NeMo Fabric skill directories must contain a SKILL.md file");
|
|
234
|
+
}
|
|
235
|
+
resolved.push(candidate);
|
|
236
|
+
}
|
|
237
|
+
return resolved;
|
|
238
|
+
}
|
|
239
|
+
function credentialValue(input, name) {
|
|
240
|
+
return input.runtimeContext.environment.env?.[name] ?? process.env[name];
|
|
241
|
+
}
|
|
242
|
+
function promptText(message) {
|
|
243
|
+
if (!Array.isArray(message.content)) {
|
|
244
|
+
return "";
|
|
245
|
+
}
|
|
246
|
+
return message.content
|
|
247
|
+
.filter((block) => {
|
|
248
|
+
return (typeof block === "object" &&
|
|
249
|
+
block !== null &&
|
|
250
|
+
"type" in block &&
|
|
251
|
+
block.type === "text" &&
|
|
252
|
+
"text" in block &&
|
|
253
|
+
typeof block.text === "string");
|
|
254
|
+
})
|
|
255
|
+
.map((block) => block.text)
|
|
256
|
+
.join("");
|
|
257
|
+
}
|
|
258
|
+
function unsupportedSessionAction(name) {
|
|
259
|
+
throw new LifecycleError("pi_unsupported_session_operation", `Pi session operation ${name} is not supported`);
|
|
260
|
+
}
|
|
261
|
+
class PiSdkSessionHandle {
|
|
262
|
+
session;
|
|
263
|
+
state;
|
|
264
|
+
stopped = false;
|
|
265
|
+
constructor(session, state) {
|
|
266
|
+
this.session = session;
|
|
267
|
+
this.state = state;
|
|
268
|
+
}
|
|
269
|
+
async prompt(text) {
|
|
270
|
+
let accepted = false;
|
|
271
|
+
let finalAssistant;
|
|
272
|
+
const unsubscribe = this.session.subscribe((event) => {
|
|
273
|
+
if (event.type === "message_end" && event.message.role === "assistant") {
|
|
274
|
+
finalAssistant = event.message;
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
try {
|
|
278
|
+
await this.session.prompt(text, {
|
|
279
|
+
expandPromptTemplates: true,
|
|
280
|
+
source: "interactive",
|
|
281
|
+
preflightResult: (result) => {
|
|
282
|
+
accepted = result;
|
|
283
|
+
},
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
finally {
|
|
287
|
+
unsubscribe();
|
|
288
|
+
}
|
|
289
|
+
return {
|
|
290
|
+
accepted,
|
|
291
|
+
text: finalAssistant === undefined ? undefined : promptText(finalAssistant),
|
|
292
|
+
stopReason: finalAssistant?.stopReason,
|
|
293
|
+
errorMessage: finalAssistant?.errorMessage,
|
|
294
|
+
shutdownRequested: this.state.shutdownRequested,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
async stop() {
|
|
298
|
+
if (this.stopped) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
this.stopped = true;
|
|
302
|
+
let failure;
|
|
303
|
+
try {
|
|
304
|
+
await this.session.abort();
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
failure = error;
|
|
308
|
+
}
|
|
309
|
+
try {
|
|
310
|
+
await this.session.extensionRunner.emit({ type: "session_shutdown", reason: "quit" });
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
failure ??= error;
|
|
314
|
+
}
|
|
315
|
+
try {
|
|
316
|
+
this.session.dispose();
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
failure ??= error;
|
|
320
|
+
}
|
|
321
|
+
if (failure !== undefined) {
|
|
322
|
+
throw failure;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
export class PiSdkSessionFactory {
|
|
327
|
+
async create(input) {
|
|
328
|
+
const systemInstruction = input.config.instructions?.system;
|
|
329
|
+
if (systemInstruction?.mode === "append") {
|
|
330
|
+
throw new LifecycleError("unsupported_system_instruction_mode", "Pi does not support instructions.system.mode='append'; supported modes: replace", {
|
|
331
|
+
metadata: {
|
|
332
|
+
field: "instructions.system.mode",
|
|
333
|
+
mode: systemInstruction.mode,
|
|
334
|
+
supported_modes: ["replace"],
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
const pi = await loadPiSdk();
|
|
339
|
+
let workspace;
|
|
340
|
+
try {
|
|
341
|
+
workspace = await realpath(resolve(input.runtimeContext.environment.workspace ?? input.baseDir));
|
|
342
|
+
if (!(await stat(workspace)).isDirectory()) {
|
|
343
|
+
throw new Error("not a directory");
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
throw new LifecycleError("pi_workspace_invalid", "The Fabric runtime workspace must be a directory");
|
|
348
|
+
}
|
|
349
|
+
const selected = selectModel(input.config);
|
|
350
|
+
const apiKeyEnv = selected.api_key_env;
|
|
351
|
+
if (apiKeyEnv === undefined || apiKeyEnv === null || apiKeyEnv.length === 0) {
|
|
352
|
+
throw new LifecycleError("pi_api_key_env_required", "The selected Pi model requires api_key_env");
|
|
353
|
+
}
|
|
354
|
+
const apiKey = credentialValue(input, apiKeyEnv);
|
|
355
|
+
if (apiKey === undefined || apiKey.length === 0) {
|
|
356
|
+
throw new LifecycleError("pi_credential_missing", `Credential environment variable ${apiKeyEnv} is not set`);
|
|
357
|
+
}
|
|
358
|
+
const settings = pi.SettingsManager.inMemory({}, { projectTrusted: false });
|
|
359
|
+
const extensionPaths = await resolveExtensionPaths(workspace, harnessSettings(input.config).extensions);
|
|
360
|
+
const skillPaths = await resolveSkillPaths(input.baseDir, input.config.skills?.paths ?? []);
|
|
361
|
+
const customTools = await resolveCustomTools(workspace, input.config.tools?.definitions ?? {});
|
|
362
|
+
const agentDir = join(workspace, ".fabric-pi");
|
|
363
|
+
const resourceLoader = new pi.DefaultResourceLoader({
|
|
364
|
+
cwd: workspace,
|
|
365
|
+
agentDir,
|
|
366
|
+
settingsManager: settings,
|
|
367
|
+
additionalExtensionPaths: extensionPaths,
|
|
368
|
+
additionalSkillPaths: skillPaths,
|
|
369
|
+
noExtensions: true,
|
|
370
|
+
noSkills: true,
|
|
371
|
+
noPromptTemplates: true,
|
|
372
|
+
noThemes: true,
|
|
373
|
+
noContextFiles: true,
|
|
374
|
+
systemPrompt: systemInstruction?.content,
|
|
375
|
+
});
|
|
376
|
+
await resourceLoader.reload();
|
|
377
|
+
const extensionErrors = resourceLoader.getExtensions().errors;
|
|
378
|
+
if (extensionErrors.length > 0) {
|
|
379
|
+
throw new LifecycleError("pi_extension_load_failed", "One or more configured Pi extensions failed to load", {
|
|
380
|
+
metadata: { count: extensionErrors.length },
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
rejectToolCollisions(customTools, extensionToolNames(resourceLoader));
|
|
384
|
+
const skillDiagnostics = resourceLoader.getSkills().diagnostics;
|
|
385
|
+
const blockingSkillDiagnostics = skillDiagnostics.filter((diagnostic) => diagnostic.type === "error" || diagnostic.type === "collision");
|
|
386
|
+
for (const diagnostic of skillDiagnostics.filter((entry) => entry.type === "warning")) {
|
|
387
|
+
process.stderr.write(`Pi skill warning: ${diagnostic.message}\n`);
|
|
388
|
+
}
|
|
389
|
+
if (blockingSkillDiagnostics.length > 0) {
|
|
390
|
+
throw new LifecycleError("pi_skill_load_failed", "One or more configured NeMo Fabric skills failed to load", {
|
|
391
|
+
metadata: { count: blockingSkillDiagnostics.length },
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
const credentials = new pi.InMemoryCredentialStore();
|
|
395
|
+
const modelRuntime = await pi.ModelRuntime.create({
|
|
396
|
+
credentials,
|
|
397
|
+
modelsPath: null,
|
|
398
|
+
allowModelNetwork: false,
|
|
399
|
+
refreshOnCreate: false,
|
|
400
|
+
});
|
|
401
|
+
await modelRuntime.setRuntimeApiKey(selected.provider, apiKey);
|
|
402
|
+
const catalogModel = modelRuntime.getModel(selected.provider, selected.model);
|
|
403
|
+
if (catalogModel === undefined) {
|
|
404
|
+
throw new LifecycleError("pi_model_unknown", "The selected provider and model are not present in Pi's catalog");
|
|
405
|
+
}
|
|
406
|
+
const model = selected.base_url ? { ...catalogModel, baseUrl: selected.base_url } : catalogModel;
|
|
407
|
+
const enabled = input.config.tools?.enabled;
|
|
408
|
+
const blocked = input.config.tools?.blocked ?? [];
|
|
409
|
+
const state = { shutdownRequested: false };
|
|
410
|
+
const { session } = await pi.createAgentSession({
|
|
411
|
+
cwd: workspace,
|
|
412
|
+
agentDir,
|
|
413
|
+
model,
|
|
414
|
+
modelRuntime,
|
|
415
|
+
resourceLoader,
|
|
416
|
+
sessionManager: pi.SessionManager.inMemory(workspace),
|
|
417
|
+
settingsManager: settings,
|
|
418
|
+
customTools,
|
|
419
|
+
tools: enabled === null ? undefined : enabled,
|
|
420
|
+
excludeTools: blocked,
|
|
421
|
+
});
|
|
422
|
+
const handle = new PiSdkSessionHandle(session, state);
|
|
423
|
+
try {
|
|
424
|
+
const blockedNames = new Set(blocked);
|
|
425
|
+
const availableNames = new Set(session.getAllTools().map((tool) => tool.name));
|
|
426
|
+
const missing = (enabled ?? []).filter((name) => !blockedNames.has(name) && !availableNames.has(name));
|
|
427
|
+
if (missing.length > 0) {
|
|
428
|
+
throw new LifecycleError("pi_tool_missing", "One or more enabled tools are not registered", {
|
|
429
|
+
metadata: { tools: missing },
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
const commandContextActions = {
|
|
433
|
+
waitForIdle: () => session.waitForIdle(),
|
|
434
|
+
newSession: async () => unsupportedSessionAction("newSession"),
|
|
435
|
+
fork: async () => unsupportedSessionAction("fork"),
|
|
436
|
+
navigateTree: async () => unsupportedSessionAction("navigateTree"),
|
|
437
|
+
switchSession: async () => unsupportedSessionAction("switchSession"),
|
|
438
|
+
reload: async () => unsupportedSessionAction("reload"),
|
|
439
|
+
};
|
|
440
|
+
await session.bindExtensions({
|
|
441
|
+
mode: "print",
|
|
442
|
+
commandContextActions,
|
|
443
|
+
abortHandler: () => {
|
|
444
|
+
void session.abort();
|
|
445
|
+
},
|
|
446
|
+
shutdownHandler: () => {
|
|
447
|
+
state.shutdownRequested = true;
|
|
448
|
+
void session.abort();
|
|
449
|
+
},
|
|
450
|
+
onError: () => {
|
|
451
|
+
process.stderr.write("Pi extension handler failed\n");
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
return handle;
|
|
455
|
+
}
|
|
456
|
+
catch (error) {
|
|
457
|
+
try {
|
|
458
|
+
await handle.stop();
|
|
459
|
+
}
|
|
460
|
+
catch {
|
|
461
|
+
process.stderr.write("Pi session cleanup failed after adapter startup error\n");
|
|
462
|
+
}
|
|
463
|
+
throw error;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AgentRunRequest, AgentRunResult, RuntimeContext } from "nemo-fabric-adapter-contract";
|
|
2
|
+
import { type AdapterRuntime, type AdapterStartInput } from "nemo-fabric-adapters-common";
|
|
3
|
+
export type PiStopReason = "stop" | "length" | "toolUse" | "error" | "aborted" | string;
|
|
4
|
+
export interface PiPromptOutcome {
|
|
5
|
+
accepted: boolean;
|
|
6
|
+
text?: string;
|
|
7
|
+
stopReason?: PiStopReason;
|
|
8
|
+
errorMessage?: string;
|
|
9
|
+
shutdownRequested?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface PiSessionHandle {
|
|
12
|
+
prompt(text: string): Promise<PiPromptOutcome>;
|
|
13
|
+
stop(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
export interface PiSessionFactory {
|
|
16
|
+
create(input: AdapterStartInput): Promise<PiSessionHandle>;
|
|
17
|
+
}
|
|
18
|
+
export declare class PiAdapterRuntime implements AdapterRuntime {
|
|
19
|
+
private readonly factory;
|
|
20
|
+
private session?;
|
|
21
|
+
private unusable;
|
|
22
|
+
constructor(factory: PiSessionFactory);
|
|
23
|
+
start(input: AdapterStartInput): Promise<void>;
|
|
24
|
+
invoke(request: AgentRunRequest, _context: RuntimeContext): Promise<AgentRunResult>;
|
|
25
|
+
stop(): Promise<void>;
|
|
26
|
+
}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { LifecycleError } from "nemo-fabric-adapters-common";
|
|
4
|
+
function failed(code, message) {
|
|
5
|
+
return {
|
|
6
|
+
status: "failed",
|
|
7
|
+
output: null,
|
|
8
|
+
error: { code, message, retryable: false },
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export class PiAdapterRuntime {
|
|
12
|
+
factory;
|
|
13
|
+
session;
|
|
14
|
+
unusable = false;
|
|
15
|
+
constructor(factory) {
|
|
16
|
+
this.factory = factory;
|
|
17
|
+
}
|
|
18
|
+
async start(input) {
|
|
19
|
+
if (this.session !== undefined) {
|
|
20
|
+
throw new LifecycleError("pi_already_started", "Pi adapter runtime is already started");
|
|
21
|
+
}
|
|
22
|
+
this.session = await this.factory.create(input);
|
|
23
|
+
this.unusable = false;
|
|
24
|
+
}
|
|
25
|
+
async invoke(request, _context) {
|
|
26
|
+
if (this.session === undefined) {
|
|
27
|
+
throw new LifecycleError("pi_not_started", "Pi adapter runtime is not started");
|
|
28
|
+
}
|
|
29
|
+
if (this.unusable) {
|
|
30
|
+
throw new LifecycleError("pi_runtime_unusable", "Pi adapter runtime cannot accept another invocation");
|
|
31
|
+
}
|
|
32
|
+
if (typeof request.input !== "string") {
|
|
33
|
+
return failed("pi_unsupported_input", "The Pi adapter accepts only plain-text input");
|
|
34
|
+
}
|
|
35
|
+
const outcome = await this.session.prompt(request.input);
|
|
36
|
+
if (!outcome.accepted) {
|
|
37
|
+
return failed("pi_prompt_rejected", "Pi rejected the prompt before starting an agent run");
|
|
38
|
+
}
|
|
39
|
+
if (outcome.shutdownRequested || outcome.stopReason === "aborted") {
|
|
40
|
+
if (outcome.shutdownRequested) {
|
|
41
|
+
this.unusable = true;
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
status: "cancelled",
|
|
45
|
+
output: null,
|
|
46
|
+
error: {
|
|
47
|
+
code: outcome.shutdownRequested ? "pi_extension_shutdown" : "pi_aborted",
|
|
48
|
+
message: outcome.shutdownRequested
|
|
49
|
+
? "A Pi extension requested adapter shutdown"
|
|
50
|
+
: "The Pi invocation was aborted",
|
|
51
|
+
retryable: false,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (outcome.stopReason === "error") {
|
|
56
|
+
return failed("pi_model_error", outcome.errorMessage || "The Pi model invocation failed");
|
|
57
|
+
}
|
|
58
|
+
if (outcome.text === undefined || outcome.text.length === 0) {
|
|
59
|
+
return failed("pi_no_assistant_response", "Pi completed without a final assistant text response");
|
|
60
|
+
}
|
|
61
|
+
return { status: "succeeded", output: { response: outcome.text } };
|
|
62
|
+
}
|
|
63
|
+
async stop() {
|
|
64
|
+
const session = this.session;
|
|
65
|
+
this.session = undefined;
|
|
66
|
+
this.unusable = false;
|
|
67
|
+
if (session !== undefined) {
|
|
68
|
+
await session.stop();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nemo-fabric-adapters-pi",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0-beta.2",
|
|
4
|
+
"description": "Pi SDK harness adapter for NVIDIA NeMo Fabric.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"exports": {
|
|
9
|
+
"./descriptor": "./pi.fabric-adapter.json",
|
|
10
|
+
"./package.json": "./package.json"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"nemo-fabric-adapters-pi": "dist/cli.js"
|
|
14
|
+
},
|
|
6
15
|
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"pi.fabric-adapter.json",
|
|
7
18
|
"README.md",
|
|
8
19
|
"LICENSE"
|
|
9
20
|
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.build.json",
|
|
23
|
+
"clean": "rm -rf dist",
|
|
24
|
+
"pack:check": "npm run build && node ../scripts/check-package.mjs",
|
|
25
|
+
"prepack": "npm run build",
|
|
26
|
+
"test": "npm run clean && npm run build && node --test test/*.test.mjs"
|
|
27
|
+
},
|
|
10
28
|
"publishConfig": {
|
|
11
29
|
"access": "public",
|
|
12
30
|
"registry": "https://registry.npmjs.org"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/NVIDIA/NeMo-Fabric.git",
|
|
35
|
+
"directory": "adapters/typescript/pi"
|
|
36
|
+
},
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/NVIDIA/NeMo-Fabric/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/NVIDIA/NeMo-Fabric/tree/main/adapters/typescript/pi#readme",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=22.19.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"jiti": "2.7.0",
|
|
46
|
+
"nemo-fabric-adapter-contract": "0.3.0-beta.2",
|
|
47
|
+
"nemo-fabric-adapters-common": "0.3.0-beta.2"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@earendil-works/pi-ai": "^0.84.2",
|
|
51
|
+
"@earendil-works/pi-coding-agent": "^0.84.2"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"@earendil-works/pi-ai": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"@earendil-works/pi-coding-agent": {
|
|
58
|
+
"optional": true
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@earendil-works/pi-ai": "0.84.2",
|
|
63
|
+
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
64
|
+
"@types/node": "24.12.4",
|
|
65
|
+
"typescript": "5.9.3"
|
|
13
66
|
}
|
|
14
67
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"contract_version": "fabric.adapter/v1alpha2",
|
|
3
|
+
"adapter_id": "nvidia.fabric.pi",
|
|
4
|
+
"adapter_kind": "process",
|
|
5
|
+
"runner": {
|
|
6
|
+
"command": "node",
|
|
7
|
+
"script": "dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"requirements": {
|
|
10
|
+
"binaries": ["node"]
|
|
11
|
+
},
|
|
12
|
+
"config": {
|
|
13
|
+
"accepts": [
|
|
14
|
+
"models",
|
|
15
|
+
"models.base_url",
|
|
16
|
+
"instructions.system",
|
|
17
|
+
"tools.definitions",
|
|
18
|
+
"tools.enabled",
|
|
19
|
+
"tools.blocked",
|
|
20
|
+
"skills"
|
|
21
|
+
],
|
|
22
|
+
"system_instruction_modes": ["replace"]
|
|
23
|
+
},
|
|
24
|
+
"settings_schema": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"extensions": {
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1
|
|
32
|
+
},
|
|
33
|
+
"uniqueItems": true
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"additionalProperties": false
|
|
37
|
+
},
|
|
38
|
+
"model_schema": {
|
|
39
|
+
"type": "object",
|
|
40
|
+
"properties": {
|
|
41
|
+
"provider": {"type": "string", "minLength": 1},
|
|
42
|
+
"model": {"type": "string", "minLength": 1},
|
|
43
|
+
"api_key_env": {"type": "string", "minLength": 1},
|
|
44
|
+
"base_url": {"type": ["string", "null"], "format": "uri"}
|
|
45
|
+
},
|
|
46
|
+
"required": ["provider", "model", "api_key_env"],
|
|
47
|
+
"additionalProperties": false
|
|
48
|
+
},
|
|
49
|
+
"tool_definition_schema": {
|
|
50
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {
|
|
53
|
+
"kind": {"const": "module"},
|
|
54
|
+
"ref": {
|
|
55
|
+
"type": "string",
|
|
56
|
+
"minLength": 1,
|
|
57
|
+
"pattern": "^[^#]+(?:#[A-Za-z_$][A-Za-z0-9_$]*)?$"
|
|
58
|
+
},
|
|
59
|
+
"settings": {"type": "object"}
|
|
60
|
+
},
|
|
61
|
+
"required": ["kind", "ref"],
|
|
62
|
+
"additionalProperties": false
|
|
63
|
+
},
|
|
64
|
+
"capabilities": {
|
|
65
|
+
"streaming": false,
|
|
66
|
+
"cancellation": false,
|
|
67
|
+
"updates": false,
|
|
68
|
+
"service": false
|
|
69
|
+
}
|
|
70
|
+
}
|