@magnitudedev/pi-extension 0.0.1-alpha.0 → 0.0.1-alpha.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 +44 -3
- package/dist/magnitude-plugin.json +5 -4
- package/dist/magnitude.js +441 -83
- package/dist/skills/magnitude/SKILL.md +16 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -3,15 +3,42 @@
|
|
|
3
3
|
This Pi package adds Magnitude model management commands and live local-inference progress to Pi's
|
|
4
4
|
built-in working row above the editor.
|
|
5
5
|
|
|
6
|
-
Requires Pi 0.83.0 or newer
|
|
6
|
+
Requires Pi 0.83.0 or newer. Install from your terminal:
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
pi install npm:@magnitudedev/pi-extension
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
On the next fresh interactive Pi launch, Magnitude asks **“Set up local models with Magnitude now?”**
|
|
13
|
+
Yes opens Magnitude's actual graphical setup in the same terminal, including its model rankings,
|
|
14
|
+
radar, downloads, and loading progress. It connects Pi automatically at the final step, then returns
|
|
15
|
+
to the same Pi conversation with the selected model active. No agent prompt or cloud credentials
|
|
16
|
+
are needed. If Magnitude is missing, accepting setup installs the CLI with
|
|
17
|
+
`npm install --global @magnitudedev/cli` before opening onboarding. Pi stays visible with its native
|
|
18
|
+
spinner: “Installing Magnitude…”. Installer output is suppressed; failures
|
|
19
|
+
show a concise diagnostic. Escape cancels preparation, and `/magnitude-setup` retries.
|
|
20
|
+
An existing CLI is preserved and must support hosted setup; an incompatible version reports an
|
|
21
|
+
update requirement rather than being silently replaced.
|
|
22
|
+
|
|
23
|
+
No or Escape leaves “You can set up local models anytime with `/magnitude-setup`.” in the conversation.
|
|
24
|
+
The offer is remembered per Pi profile across restarts, reloads, and package updates. Existing
|
|
25
|
+
Magnitude model configurations, non-interactive modes, conversations, and startup prompts are left
|
|
26
|
+
alone. Run `/magnitude-setup` whenever you want to start onboarding yourself.
|
|
27
|
+
|
|
28
|
+
The package includes the Magnitude usage skill; an already-loaded skill of that name takes
|
|
29
|
+
precedence without a collision warning. `--no-skills` disables this fallback too. No separate CLI
|
|
30
|
+
installation is needed. npm must be available with a writable global prefix, as for a normal npm
|
|
31
|
+
CLI installation. Merely loading the extension does not install software.
|
|
32
|
+
|
|
33
|
+
If you already have Magnitude and a local model, connect directly:
|
|
7
34
|
|
|
8
35
|
```sh
|
|
9
36
|
magnitude connections add pi
|
|
10
37
|
```
|
|
11
38
|
|
|
12
39
|
Restart Pi or run `/reload` after connecting. `PI_CODING_AGENT_DIR`, if set, is honored.
|
|
13
|
-
Installing the package directly
|
|
14
|
-
install the Magnitude CLI, start its service
|
|
40
|
+
Installing the package directly does not configure provider models,
|
|
41
|
+
install the Magnitude CLI, or start its service; the connection command
|
|
15
42
|
does all connection configuration and can be run safely after a standalone package install.
|
|
16
43
|
|
|
17
44
|
The extension bundles Magnitude's private SDK. Model status, loading, and stopping use the existing
|
|
@@ -28,6 +55,7 @@ latency, and token-weighted generation throughput. Pi extensions execute with yo
|
|
|
28
55
|
|
|
29
56
|
Commands:
|
|
30
57
|
|
|
58
|
+
- `/magnitude-setup` — open Magnitude's graphical model setup inside Pi
|
|
31
59
|
- `/load-model [model-id]` — load an installed model
|
|
32
60
|
- `/stop-model` — stop the active model
|
|
33
61
|
|
|
@@ -52,3 +80,16 @@ builds and runs the checkout's inference runtime,
|
|
|
52
80
|
and keeps the current source CLI available until Pi exits. Exiting Pi stops the development runtime
|
|
53
81
|
and restores the service state that existed before launch. The launcher inherits the current
|
|
54
82
|
environment; it does not start or configure tracing.
|
|
83
|
+
|
|
84
|
+
To test the first-run setup offer with no preconfigured Magnitude provider:
|
|
85
|
+
|
|
86
|
+
```sh
|
|
87
|
+
bun run dev:pi --setup
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
This installs only the checkout's package into the temporary Pi profile. Accept the setup offer,
|
|
91
|
+
choose a model, and return to Pi to chat. Both development modes keep connection files and skills
|
|
92
|
+
inside the temporary profile, and do not change login-startup registration. They share the machine's
|
|
93
|
+
real model storage and inference service: downloads consume disk space and selected models use
|
|
94
|
+
memory. The launcher restores the prior managed service after Pi exits. Ordinary installed setup
|
|
95
|
+
does enable login startup.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magnitudedev/pi-extension",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.2",
|
|
4
4
|
"rpcVersion": 1,
|
|
5
|
-
"contentFingerprint": "
|
|
5
|
+
"contentFingerprint": "5661646da7080511725cdef888b7ce04148c99b56fbe6454619754ebc5235718",
|
|
6
6
|
"files": {
|
|
7
|
-
"README.md": "
|
|
8
|
-
"dist/magnitude.js": "
|
|
7
|
+
"README.md": "0334364928717efd1b146486d6dc2d9ff0f28b77ff28b162d04351738170b581",
|
|
8
|
+
"dist/magnitude.js": "915f1d6bb080180a774b1bd009b3d7bc6c5cbab94fd7ab067dd3345d638538ad",
|
|
9
|
+
"dist/skills/magnitude/SKILL.md": "37de67f0c7b831415f6298c8785b7ae97d5cfe454dc778651d96114840d2ace2"
|
|
9
10
|
}
|
|
10
11
|
}
|
package/dist/magnitude.js
CHANGED
|
@@ -3364,13 +3364,14 @@ var makeClient = (options) => Effect6.gen(function* () {
|
|
|
3364
3364
|
yield* Deferred2.fail(active, new ConnectionClosed({}));
|
|
3365
3365
|
})).pipe(Effect6.zipRight(Scope2.close(scope, Exit.void))));
|
|
3366
3366
|
const unavailable = (message) => new ServiceUnavailable({ origin, message });
|
|
3367
|
+
const decodeHealth = Schema47.decodeUnknown(Schema47.Union(MagnitudeHealthResponseSchema, AcnHealthResponseSchema));
|
|
3367
3368
|
const probe = http.get(`${origin}/health`).pipe(Effect6.timeoutFail({
|
|
3368
3369
|
duration: "2 seconds",
|
|
3369
3370
|
onTimeout: () => unavailable("Magnitude service health timed out")
|
|
3370
3371
|
}), Effect6.mapError((error) => error instanceof ServiceUnavailable ? error : unavailable("Magnitude service is unavailable")), Effect6.flatMap((response) => response.status !== 200 && response.status !== 503 ? Effect6.fail(new InvalidServiceResponse({
|
|
3371
3372
|
origin,
|
|
3372
3373
|
message: `Health returned HTTP ${response.status}`
|
|
3373
|
-
})) : response.json.pipe(Effect6.flatMap(
|
|
3374
|
+
})) : response.json.pipe(Effect6.flatMap(decodeHealth), Effect6.map((health) => ("rpcVersion" in health) ? health : { ...health, rpcVersion: 0 }), Effect6.mapError(() => new InvalidServiceResponse({
|
|
3374
3375
|
origin,
|
|
3375
3376
|
message: "Invalid Magnitude health response"
|
|
3376
3377
|
})))));
|
|
@@ -3391,36 +3392,35 @@ var makeClient = (options) => Effect6.gen(function* () {
|
|
|
3391
3392
|
version: health.version,
|
|
3392
3393
|
rpcVersion: health.rpcVersion
|
|
3393
3394
|
});
|
|
3395
|
+
const startService = (start2) => Effect6.scoped(Effect6.gen(function* () {
|
|
3396
|
+
yield* report({ _tag: "Starting", phase: "PreparingAcn" });
|
|
3397
|
+
yield* probe.pipe(Effect6.flatMap((health) => Option13.match(serviceProgressFromHealth(health.state), {
|
|
3398
|
+
onNone: () => Effect6.void,
|
|
3399
|
+
onSome: report
|
|
3400
|
+
})), Effect6.ignore, Effect6.zipRight(Effect6.sleep("250 millis")), Effect6.forever, Effect6.forkScoped);
|
|
3401
|
+
yield* start2.pipe(Stream4.runForEach(report));
|
|
3402
|
+
}));
|
|
3394
3403
|
const acquire = Effect6.gen(function* () {
|
|
3395
3404
|
let started = false;
|
|
3396
3405
|
while (true) {
|
|
3397
|
-
const observed = yield* Effect6.either(probe);
|
|
3398
|
-
if (observed._tag === "
|
|
3399
|
-
|
|
3400
|
-
return yield* observed.left;
|
|
3401
|
-
if (!started && Option13.isSome(starter)) {
|
|
3402
|
-
started = true;
|
|
3403
|
-
yield* report({ _tag: "Starting", phase: "PreparingAcn" });
|
|
3404
|
-
yield* Effect6.scoped(Effect6.gen(function* () {
|
|
3405
|
-
yield* probe.pipe(Effect6.flatMap((health) => Option13.match(serviceProgressFromHealth(health.state), {
|
|
3406
|
-
onNone: () => Effect6.void,
|
|
3407
|
-
onSome: report
|
|
3408
|
-
})), Effect6.ignore, Effect6.zipRight(Effect6.sleep("250 millis")), Effect6.forever, Effect6.forkScoped);
|
|
3409
|
-
yield* starter.value.start.pipe(Stream4.runForEach(report));
|
|
3410
|
-
}));
|
|
3411
|
-
continue;
|
|
3412
|
-
}
|
|
3413
|
-
if (!started)
|
|
3414
|
-
return yield* observed.left;
|
|
3415
|
-
} else {
|
|
3416
|
-
const health = observed.right;
|
|
3417
|
-
const service = yield* validate(health);
|
|
3406
|
+
const observed = yield* Effect6.either(probe.pipe(Effect6.flatMap((health) => validate(health).pipe(Effect6.map((service) => ({ health, service }))))));
|
|
3407
|
+
if (observed._tag === "Right") {
|
|
3408
|
+
const { health, service } = observed.right;
|
|
3418
3409
|
if (health.state._tag === "Ready")
|
|
3419
3410
|
return service;
|
|
3420
3411
|
yield* Option13.match(serviceProgressFromHealth(health.state), {
|
|
3421
3412
|
onNone: () => Effect6.void,
|
|
3422
3413
|
onSome: report
|
|
3423
3414
|
});
|
|
3415
|
+
} else if (started) {
|
|
3416
|
+
if (observed.left._tag !== "ServiceUnavailable")
|
|
3417
|
+
return yield* observed.left;
|
|
3418
|
+
} else if (Option13.isSome(starter)) {
|
|
3419
|
+
started = true;
|
|
3420
|
+
yield* startService(starter.value.start);
|
|
3421
|
+
continue;
|
|
3422
|
+
} else {
|
|
3423
|
+
return yield* observed.left;
|
|
3424
3424
|
}
|
|
3425
3425
|
yield* Effect6.sleep("250 millis");
|
|
3426
3426
|
}
|
|
@@ -5841,16 +5841,372 @@ var registerMagnitudeCommands = (pi, sdk = clientLayer2()) => {
|
|
|
5841
5841
|
};
|
|
5842
5842
|
};
|
|
5843
5843
|
|
|
5844
|
+
// extensions/onboarding.ts
|
|
5845
|
+
import * as FileSystem3 from "@effect/platform/FileSystem";
|
|
5846
|
+
import * as NodeFileSystem2 from "@effect/platform-node/NodeFileSystem";
|
|
5847
|
+
import { getAgentDir, parseArgs } from "@earendil-works/pi-coding-agent";
|
|
5848
|
+
import { Effect as Effect13, Exit as Exit4, Scope as Scope5 } from "effect";
|
|
5849
|
+
import { fileURLToPath } from "node:url";
|
|
5850
|
+
|
|
5851
|
+
// extensions/setup.ts
|
|
5852
|
+
import * as Command2 from "@effect/platform/Command";
|
|
5853
|
+
import * as CommandExecutor3 from "@effect/platform/CommandExecutor";
|
|
5854
|
+
import * as FileSystem from "@effect/platform/FileSystem";
|
|
5855
|
+
import * as NodeContext from "@effect/platform-node/NodeContext";
|
|
5856
|
+
import { delimiter, join } from "node:path";
|
|
5857
|
+
import { stripVTControlCharacters } from "node:util";
|
|
5858
|
+
import { Context as Context7, Effect as Effect12, Exit as Exit3, Fiber as Fiber2, Layer as Layer6, ManagedRuntime as ManagedRuntime2, Schema as Schema53, Scope as Scope4, Stream as Stream7 } from "effect";
|
|
5859
|
+
import { CancellableLoader } from "@earendil-works/pi-tui";
|
|
5860
|
+
|
|
5861
|
+
// ../../packages/utils/src/process/interactive-process.ts
|
|
5862
|
+
import { spawn } from "node:child_process";
|
|
5863
|
+
import { Deferred as Deferred3, Duration as Duration2, Effect as Effect11, Option as Option17, Schema as Schema51 } from "effect";
|
|
5864
|
+
|
|
5865
|
+
class InteractiveProcessFailed extends Schema51.TaggedError()("InteractiveProcessFailed", {
|
|
5866
|
+
operation: Schema51.Literal("spawn", "run"),
|
|
5867
|
+
message: Schema51.String
|
|
5868
|
+
}) {
|
|
5869
|
+
}
|
|
5870
|
+
var messageFrom = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
5871
|
+
var definedEnvironment = (environment) => Object.fromEntries(Object.entries(environment).filter((entry) => entry[1] !== undefined));
|
|
5872
|
+
var acquireInteractiveProcess = (input) => Effect11.gen(function* () {
|
|
5873
|
+
const termination = yield* Deferred3.make();
|
|
5874
|
+
const child = yield* Effect11.async((resume) => {
|
|
5875
|
+
let acquired = false;
|
|
5876
|
+
let handle;
|
|
5877
|
+
try {
|
|
5878
|
+
handle = spawn(input.executable, [...input.args], {
|
|
5879
|
+
cwd: input.workingDirectory,
|
|
5880
|
+
detached: false,
|
|
5881
|
+
env: definedEnvironment(input.environment),
|
|
5882
|
+
shell: false,
|
|
5883
|
+
stdio: "inherit"
|
|
5884
|
+
});
|
|
5885
|
+
} catch (cause) {
|
|
5886
|
+
resume(Effect11.fail(new InteractiveProcessFailed({
|
|
5887
|
+
operation: "spawn",
|
|
5888
|
+
message: messageFrom(cause)
|
|
5889
|
+
})));
|
|
5890
|
+
return;
|
|
5891
|
+
}
|
|
5892
|
+
const onError = (cause) => {
|
|
5893
|
+
const failure = new InteractiveProcessFailed({
|
|
5894
|
+
operation: acquired ? "run" : "spawn",
|
|
5895
|
+
message: messageFrom(cause)
|
|
5896
|
+
});
|
|
5897
|
+
if (acquired) {
|
|
5898
|
+
Deferred3.unsafeDone(termination, Effect11.fail(failure));
|
|
5899
|
+
} else {
|
|
5900
|
+
resume(Effect11.fail(failure));
|
|
5901
|
+
}
|
|
5902
|
+
};
|
|
5903
|
+
const onExit = (code, signal) => {
|
|
5904
|
+
Deferred3.unsafeDone(termination, Effect11.succeed(code === null ? { _tag: "Signaled", signal } : { _tag: "Exited", code }));
|
|
5905
|
+
};
|
|
5906
|
+
const onSpawn = () => {
|
|
5907
|
+
acquired = true;
|
|
5908
|
+
resume(Effect11.succeed(handle));
|
|
5909
|
+
};
|
|
5910
|
+
handle.once("error", onError);
|
|
5911
|
+
handle.once("exit", onExit);
|
|
5912
|
+
handle.once("spawn", onSpawn);
|
|
5913
|
+
return Effect11.sync(() => {
|
|
5914
|
+
if (!acquired) {
|
|
5915
|
+
handle.removeListener("error", onError);
|
|
5916
|
+
handle.removeListener("exit", onExit);
|
|
5917
|
+
handle.removeListener("spawn", onSpawn);
|
|
5918
|
+
if (handle.exitCode === null && handle.signalCode === null) {
|
|
5919
|
+
try {
|
|
5920
|
+
handle.kill("SIGTERM");
|
|
5921
|
+
} catch {}
|
|
5922
|
+
}
|
|
5923
|
+
}
|
|
5924
|
+
});
|
|
5925
|
+
});
|
|
5926
|
+
return { child, termination };
|
|
5927
|
+
});
|
|
5928
|
+
var FORWARDED_SIGNALS = process.platform === "win32" ? ["SIGINT", "SIGTERM", "SIGBREAK"] : ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
5929
|
+
var awaitWithSignalForwarding = ({ child, termination }) => Effect11.acquireUseRelease(Effect11.sync(() => {
|
|
5930
|
+
const listeners = new Map;
|
|
5931
|
+
for (const signal of FORWARDED_SIGNALS) {
|
|
5932
|
+
const listener = () => {
|
|
5933
|
+
try {
|
|
5934
|
+
child.kill(signal);
|
|
5935
|
+
} catch {}
|
|
5936
|
+
};
|
|
5937
|
+
listeners.set(signal, listener);
|
|
5938
|
+
process.on(signal, listener);
|
|
5939
|
+
}
|
|
5940
|
+
return listeners;
|
|
5941
|
+
}), () => Deferred3.await(termination), (listeners) => Effect11.sync(() => {
|
|
5942
|
+
for (const [signal, listener] of listeners) {
|
|
5943
|
+
process.removeListener(signal, listener);
|
|
5944
|
+
}
|
|
5945
|
+
}));
|
|
5946
|
+
var terminateAndReap = ({ child, termination }) => Effect11.gen(function* () {
|
|
5947
|
+
if (yield* Deferred3.isDone(termination))
|
|
5948
|
+
return;
|
|
5949
|
+
yield* Effect11.sync(() => {
|
|
5950
|
+
try {
|
|
5951
|
+
child.kill("SIGTERM");
|
|
5952
|
+
} catch {}
|
|
5953
|
+
});
|
|
5954
|
+
const graceful = yield* Deferred3.await(termination).pipe(Effect11.ignore, Effect11.timeoutOption(Duration2.seconds(2)));
|
|
5955
|
+
if (Option17.isSome(graceful))
|
|
5956
|
+
return;
|
|
5957
|
+
yield* Effect11.sync(() => {
|
|
5958
|
+
try {
|
|
5959
|
+
child.kill("SIGKILL");
|
|
5960
|
+
} catch {}
|
|
5961
|
+
});
|
|
5962
|
+
yield* Deferred3.await(termination).pipe(Effect11.ignore, Effect11.timeout(Duration2.seconds(2)), Effect11.ignore);
|
|
5963
|
+
});
|
|
5964
|
+
var runInteractiveProcess = (input) => Effect11.acquireUseRelease(acquireInteractiveProcess(input), awaitWithSignalForwarding, terminateAndReap);
|
|
5965
|
+
// ../../packages/client-common/src/harness-connections/hosted-setup.ts
|
|
5966
|
+
import { Schema as Schema52 } from "effect";
|
|
5967
|
+
var HOSTED_SETUP_PROTOCOL_VERSION = 1;
|
|
5968
|
+
var HOSTED_SETUP_MAX_RESULT_BYTES = 16 * 1024;
|
|
5969
|
+
var HostedSetupCapability = Schema52.Struct({
|
|
5970
|
+
protocolVersion: Schema52.Literal(HOSTED_SETUP_PROTOCOL_VERSION)
|
|
5971
|
+
});
|
|
5972
|
+
var version = Schema52.Literal(HOSTED_SETUP_PROTOCOL_VERSION);
|
|
5973
|
+
var HostedSetupResult = Schema52.Union(Schema52.TaggedStruct("Completed", { protocolVersion: version, modelId: ModelIdSchema }), Schema52.TaggedStruct("Cancelled", { protocolVersion: version }), Schema52.TaggedStruct("Failed", {
|
|
5974
|
+
protocolVersion: version,
|
|
5975
|
+
message: Schema52.String.pipe(Schema52.filter((message) => new TextEncoder().encode(message).length <= 4096))
|
|
5976
|
+
}));
|
|
5977
|
+
|
|
5978
|
+
// extensions/setup.ts
|
|
5979
|
+
class PiSetupFailed extends Schema53.TaggedError()("PiSetupFailed", {
|
|
5980
|
+
message: Schema53.String
|
|
5981
|
+
}) {
|
|
5982
|
+
}
|
|
5983
|
+
var failure = (message) => new PiSetupFailed({ message });
|
|
5984
|
+
var prepareMagnitudeCli = (cwd, setMessage = () => Effect12.void) => Effect12.gen(function* () {
|
|
5985
|
+
const fs = yield* FileSystem.FileSystem;
|
|
5986
|
+
const override = process.env.MAGNITUDE_CLI?.trim();
|
|
5987
|
+
const executable = override || "magnitude";
|
|
5988
|
+
const probe = Effect12.scoped(Effect12.gen(function* () {
|
|
5989
|
+
const child = yield* Command2.make(executable, "setup", "--host-protocol").pipe(Command2.start);
|
|
5990
|
+
const [code, output, stderr] = yield* Effect12.all([
|
|
5991
|
+
child.exitCode,
|
|
5992
|
+
child.stdout.pipe(Stream7.decodeText(), Stream7.runFold("", (text2, part) => (text2 + part).slice(0, 4097))),
|
|
5993
|
+
child.stderr.pipe(Stream7.decodeText(), Stream7.runFold("", (text2, part) => (text2 + part).slice(-4096)))
|
|
5994
|
+
], { concurrency: "unbounded" });
|
|
5995
|
+
if (code !== 0 || output.length > 4096)
|
|
5996
|
+
return yield* failure(`Magnitude's setup capability check failed at ${executable} (exit ${code}). ${stripVTControlCharacters(stderr).trim().slice(-600)}${override ? " Check MAGNITUDE_CLI." : ""}`);
|
|
5997
|
+
return output;
|
|
5998
|
+
}));
|
|
5999
|
+
const capability = yield* probe.pipe(Effect12.catchIf((error) => !override && error._tag === "SystemError" && error.reason === "NotFound", (error) => Effect12.gen(function* () {
|
|
6000
|
+
for (const directory of (process.env.PATH ?? "").split(delimiter)) {
|
|
6001
|
+
if (yield* fs.exists(join(directory || cwd, executable)))
|
|
6002
|
+
return yield* error;
|
|
6003
|
+
}
|
|
6004
|
+
yield* setMessage("Installing Magnitude…");
|
|
6005
|
+
yield* Effect12.scoped(Effect12.gen(function* () {
|
|
6006
|
+
const child = yield* Command2.make("npm", "install", "--global", "@magnitudedev/cli").pipe(Command2.workingDirectory(cwd), Command2.start);
|
|
6007
|
+
const [code, , stderr] = yield* Effect12.all([
|
|
6008
|
+
child.exitCode,
|
|
6009
|
+
Stream7.runDrain(child.stdout),
|
|
6010
|
+
child.stderr.pipe(Stream7.decodeText(), Stream7.runFold("", (text2, part) => (text2 + part).slice(-4096)))
|
|
6011
|
+
], { concurrency: "unbounded" });
|
|
6012
|
+
if (code !== 0)
|
|
6013
|
+
return yield* failure(`Magnitude installation failed (exit ${code}). ${stripVTControlCharacters(stderr).trim().slice(-600)} Run /magnitude-setup to retry.`);
|
|
6014
|
+
}));
|
|
6015
|
+
return yield* probe;
|
|
6016
|
+
})), Effect12.timeout("10 minutes"), Effect12.mapError((error) => error instanceof PiSetupFailed ? error : failure(`Could not prepare Magnitude: ${String(error)}. Run /magnitude-setup to retry${override ? ", or check MAGNITUDE_CLI" : ""}.`)));
|
|
6017
|
+
yield* Schema53.decodeUnknown(Schema53.parseJson(HostedSetupCapability))(capability).pipe(Effect12.mapError(() => failure(`The Magnitude CLI at ${executable} does not support hosted setup. Update @magnitudedev/cli${override ? ", or check MAGNITUDE_CLI" : ""}.`)));
|
|
6018
|
+
return executable;
|
|
6019
|
+
});
|
|
6020
|
+
var withPiPreparation = (ctx, work) => Effect12.acquireUseRelease(Effect12.async((resume, signal) => {
|
|
6021
|
+
let closed;
|
|
6022
|
+
closed = ctx.ui.custom((tui, theme, _keys, done) => {
|
|
6023
|
+
const loader = new CancellableLoader(tui, (text2) => theme.fg("accent", text2), (text2) => theme.fg("muted", text2), "Installing Magnitude…");
|
|
6024
|
+
if (signal.aborted) {
|
|
6025
|
+
loader.dispose();
|
|
6026
|
+
done();
|
|
6027
|
+
} else
|
|
6028
|
+
resume(Effect12.succeed({ loader, finish: () => done(), closed: () => closed }));
|
|
6029
|
+
return loader;
|
|
6030
|
+
});
|
|
6031
|
+
closed.catch((error) => resume(Effect12.fail(failure(`Could not open Pi setup: ${String(error)}`))));
|
|
6032
|
+
}), ({ loader }) => work((message) => Effect12.sync(() => loader.setMessage(message))).pipe(Effect12.raceFirst(Effect12.async((resume) => {
|
|
6033
|
+
loader.onAbort = () => resume(Effect12.fail(failure("Magnitude setup cancelled. Run /magnitude-setup to retry.")));
|
|
6034
|
+
if (loader.signal.aborted)
|
|
6035
|
+
loader.onAbort();
|
|
6036
|
+
return Effect12.sync(() => {
|
|
6037
|
+
loader.onAbort = undefined;
|
|
6038
|
+
});
|
|
6039
|
+
}))), ({ loader, finish, closed }) => Effect12.sync(() => {
|
|
6040
|
+
loader.dispose();
|
|
6041
|
+
finish();
|
|
6042
|
+
}).pipe(Effect12.zipRight(Effect12.promise(closed))));
|
|
6043
|
+
var restoreHostTerminal = (tui) => tui.terminal.write("\x1B[?2026l\x1B[?1003l\x1B[?1002l\x1B[?1000l\x1B[?1006l\x1B[?1004l" + "\x1B[?2004l\x1B[>4;0m\x1B[=0u\x1B[?1049l\x1B[0m\x1B[0 q");
|
|
6044
|
+
var withPiTerminal = (ctx, work) => Effect12.acquireUseRelease(Effect12.async((resume, signal) => {
|
|
6045
|
+
let closed;
|
|
6046
|
+
closed = ctx.ui.custom((tui, _theme, _keys, done) => {
|
|
6047
|
+
if (signal.aborted)
|
|
6048
|
+
done();
|
|
6049
|
+
else
|
|
6050
|
+
resume(Effect12.succeed({ tui, finish: () => done(), closed: () => closed }));
|
|
6051
|
+
return { render: () => [], invalidate: () => {} };
|
|
6052
|
+
});
|
|
6053
|
+
closed.catch((error) => resume(Effect12.fail(failure(`Could not open Pi setup: ${String(error)}`))));
|
|
6054
|
+
}), ({ tui }) => Effect12.sync(() => tui.stop()).pipe(Effect12.zipRight(work)), ({ tui, finish, closed }) => Effect12.sync(() => {
|
|
6055
|
+
try {
|
|
6056
|
+
restoreHostTerminal(tui);
|
|
6057
|
+
tui.start();
|
|
6058
|
+
tui.requestRender(true);
|
|
6059
|
+
} finally {
|
|
6060
|
+
finish();
|
|
6061
|
+
}
|
|
6062
|
+
}).pipe(Effect12.zipRight(Effect12.promise(closed))));
|
|
6063
|
+
var validateSetupTermination = (result, termination) => {
|
|
6064
|
+
if (termination._tag !== "Exited" || termination.code === 0 !== (result._tag !== "Failed")) {
|
|
6065
|
+
return Effect12.fail(failure("Magnitude setup exited without a consistent completion result. Run /magnitude-setup to retry."));
|
|
6066
|
+
}
|
|
6067
|
+
return result._tag === "Failed" ? Effect12.fail(failure(result.message)) : Effect12.succeed(result);
|
|
6068
|
+
};
|
|
6069
|
+
var PiSetup = Context7.GenericTag("pi/PiSetup");
|
|
6070
|
+
var PiSetupLive = Layer6.effect(PiSetup, Effect12.gen(function* () {
|
|
6071
|
+
const fs = yield* FileSystem.FileSystem;
|
|
6072
|
+
const executor = yield* CommandExecutor3.CommandExecutor;
|
|
6073
|
+
return {
|
|
6074
|
+
run: (ctx) => Effect12.scoped(Effect12.gen(function* () {
|
|
6075
|
+
const directory = yield* fs.makeTempDirectoryScoped({ prefix: "magnitude-pi-setup-" });
|
|
6076
|
+
const resultPath = `${directory}/result.json`;
|
|
6077
|
+
const executable = yield* withPiPreparation(ctx, (setMessage) => prepareMagnitudeCli(ctx.cwd, setMessage));
|
|
6078
|
+
const termination = yield* withPiTerminal(ctx, runInteractiveProcess({
|
|
6079
|
+
executable,
|
|
6080
|
+
args: ["setup", "--host", "pi", "--result-file", resultPath],
|
|
6081
|
+
environment: process.env,
|
|
6082
|
+
workingDirectory: ctx.cwd
|
|
6083
|
+
}));
|
|
6084
|
+
if (termination._tag === "Signaled" && termination.signal === "SIGINT") {
|
|
6085
|
+
return { protocolVersion: HOSTED_SETUP_PROTOCOL_VERSION, _tag: "Cancelled" };
|
|
6086
|
+
}
|
|
6087
|
+
if (termination._tag === "Signaled") {
|
|
6088
|
+
return yield* failure(`Magnitude setup stopped unexpectedly (${termination.signal}). Run /magnitude-setup to retry.`);
|
|
6089
|
+
}
|
|
6090
|
+
const stat = yield* fs.stat(resultPath).pipe(Effect12.mapError(() => failure("Magnitude setup exited without a completion result. Run /magnitude-setup to retry.")));
|
|
6091
|
+
if (stat.type !== "File" || stat.size > BigInt(HOSTED_SETUP_MAX_RESULT_BYTES)) {
|
|
6092
|
+
return yield* failure("Magnitude setup returned an invalid or oversized result");
|
|
6093
|
+
}
|
|
6094
|
+
const result = yield* fs.readFileString(resultPath).pipe(Effect12.flatMap(Schema53.decodeUnknown(Schema53.parseJson(HostedSetupResult))), Effect12.mapError(() => failure("Magnitude setup did not return a valid completion result. Run /magnitude-setup to retry.")));
|
|
6095
|
+
return yield* validateSetupTermination(result, termination);
|
|
6096
|
+
})).pipe(Effect12.provideService(CommandExecutor3.CommandExecutor, executor), Effect12.provideService(FileSystem.FileSystem, fs), Effect12.mapError((error) => error instanceof PiSetupFailed ? error : failure(`Magnitude setup could not finish: ${String(error)}`)))
|
|
6097
|
+
};
|
|
6098
|
+
}));
|
|
6099
|
+
var registerMagnitudeSetup = (pi, layer4 = PiSetupLive.pipe(Layer6.provide(NodeContext.layer))) => {
|
|
6100
|
+
const runtime = ManagedRuntime2.make(layer4);
|
|
6101
|
+
const scope = Effect12.runSync(Scope4.make());
|
|
6102
|
+
const gate = Effect12.runSync(Effect12.makeSemaphore(1));
|
|
6103
|
+
const action = (ctx) => Effect12.gen(function* () {
|
|
6104
|
+
if (ctx.mode !== "tui")
|
|
6105
|
+
return yield* failure("Run /magnitude-setup in Pi's interactive terminal.");
|
|
6106
|
+
if (!ctx.isIdle() || ctx.hasPendingMessages())
|
|
6107
|
+
return yield* failure("Wait for the current task to finish, then run /magnitude-setup.");
|
|
6108
|
+
const setup = yield* PiSetup;
|
|
6109
|
+
const result = yield* setup.run(ctx);
|
|
6110
|
+
if (result._tag !== "Completed")
|
|
6111
|
+
return false;
|
|
6112
|
+
yield* Effect12.tryPromise({
|
|
6113
|
+
try: async () => {
|
|
6114
|
+
await ctx.modelRegistry.refresh();
|
|
6115
|
+
const model2 = ctx.modelRegistry.find("magnitude", result.modelId);
|
|
6116
|
+
if (!model2 || !await pi.setModel(model2))
|
|
6117
|
+
throw new Error("The selected model is not available in Pi");
|
|
6118
|
+
},
|
|
6119
|
+
catch: (error) => failure(`Magnitude setup completed, but Pi could not activate the model: ${String(error)}. Run /reload and select it with /model.`)
|
|
6120
|
+
});
|
|
6121
|
+
return true;
|
|
6122
|
+
});
|
|
6123
|
+
const run = (ctx) => runtime.runPromise(Effect12.forkIn(gate.withPermitsIfAvailable(1)(action(ctx)).pipe(Effect12.flatMap((result) => result._tag === "Some" ? Effect12.succeed(result.value) : Effect12.fail(failure("Magnitude setup is already open."))), Effect12.catchAll((error) => Effect12.sync(() => {
|
|
6124
|
+
ctx.ui.notify(error.message, "error");
|
|
6125
|
+
return false;
|
|
6126
|
+
}))), scope).pipe(Effect12.flatMap(Fiber2.join)));
|
|
6127
|
+
pi.registerCommand("magnitude-setup", {
|
|
6128
|
+
description: "Choose and set up a local Magnitude model",
|
|
6129
|
+
handler: async (_args, ctx) => {
|
|
6130
|
+
const reload = await run(ctx);
|
|
6131
|
+
if (reload) {
|
|
6132
|
+
try {
|
|
6133
|
+
await ctx.reload();
|
|
6134
|
+
} catch (error) {
|
|
6135
|
+
throw failure(`Magnitude setup completed and selected the model, but Pi could not reload its resources: ${String(error)}. Run /reload to retry.`);
|
|
6136
|
+
}
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6139
|
+
});
|
|
6140
|
+
return {
|
|
6141
|
+
run,
|
|
6142
|
+
dispose: async () => {
|
|
6143
|
+
await Effect12.runPromise(Scope4.close(scope, Exit3.void));
|
|
6144
|
+
await runtime.dispose();
|
|
6145
|
+
}
|
|
6146
|
+
};
|
|
6147
|
+
};
|
|
6148
|
+
|
|
6149
|
+
// extensions/onboarding.ts
|
|
6150
|
+
var SETUP_QUESTION = "Set up local models with Magnitude now?";
|
|
6151
|
+
var SETUP_REMINDER = "You can set up local models anytime with `/magnitude-setup`.";
|
|
6152
|
+
var claimSetupOffer = (agentDir) => Effect13.gen(function* () {
|
|
6153
|
+
const fs = yield* FileSystem3.FileSystem;
|
|
6154
|
+
const directory = `${agentDir}/magnitude`;
|
|
6155
|
+
yield* fs.makeDirectory(directory, { recursive: true });
|
|
6156
|
+
return yield* fs.writeFileString(`${directory}/setup-offered`, "", { flag: "wx" }).pipe(Effect13.as(true), Effect13.catchTag("SystemError", (error) => error.reason === "AlreadyExists" ? Effect13.succeed(false) : Effect13.fail(error)));
|
|
6157
|
+
});
|
|
6158
|
+
function canOfferSetup(ctx, argv) {
|
|
6159
|
+
if (ctx.mode !== "tui" || !ctx.isIdle() || ctx.hasPendingMessages() || ctx.ui.getEditorText().trim())
|
|
6160
|
+
return false;
|
|
6161
|
+
if (ctx.sessionManager.getEntries().some((entry) => entry.type === "message"))
|
|
6162
|
+
return false;
|
|
6163
|
+
if (ctx.modelRegistry.getAll().some((model2) => model2.provider === "magnitude"))
|
|
6164
|
+
return false;
|
|
6165
|
+
const args = parseArgs([...argv]);
|
|
6166
|
+
return args.messages.length === 0 && args.fileArgs.length === 0;
|
|
6167
|
+
}
|
|
6168
|
+
var offerSetup = (pi, ctx, agentDir, signal, runSetup) => Effect13.gen(function* () {
|
|
6169
|
+
if (!(yield* claimSetupOffer(agentDir)) || signal.aborted)
|
|
6170
|
+
return;
|
|
6171
|
+
const accepted = yield* Effect13.tryPromise(() => ctx.ui.confirm(SETUP_QUESTION, "", { signal }));
|
|
6172
|
+
if (signal.aborted)
|
|
6173
|
+
return;
|
|
6174
|
+
if (accepted && ctx.isIdle() && !ctx.hasPendingMessages()) {
|
|
6175
|
+
yield* Effect13.tryPromise(() => runSetup(ctx));
|
|
6176
|
+
} else
|
|
6177
|
+
pi.sendMessage({ customType: "magnitude-setup", content: SETUP_REMINDER, display: true });
|
|
6178
|
+
});
|
|
6179
|
+
function registerMagnitudeOnboarding(pi) {
|
|
6180
|
+
const abort = new AbortController;
|
|
6181
|
+
const scope = Effect13.runSync(Scope5.make());
|
|
6182
|
+
const setup = registerMagnitudeSetup(pi);
|
|
6183
|
+
pi.on("resources_discover", () => {
|
|
6184
|
+
if (parseArgs(process.argv.slice(2)).noSkills || pi.getCommands().some((command) => command.source === "skill" && command.name === "skill:magnitude"))
|
|
6185
|
+
return;
|
|
6186
|
+
return { skillPaths: [fileURLToPath(new URL("./skills/magnitude/SKILL.md", import.meta.url))] };
|
|
6187
|
+
});
|
|
6188
|
+
pi.on("session_start", (event, ctx) => {
|
|
6189
|
+
if (event.reason !== "startup" || !canOfferSetup(ctx, process.argv.slice(2)))
|
|
6190
|
+
return;
|
|
6191
|
+
Effect13.runFork(Effect13.forkIn(offerSetup(pi, ctx, getAgentDir(), abort.signal, setup.run).pipe(Effect13.provide(NodeFileSystem2.layer), Effect13.catchAll(() => Effect13.sync(() => ctx.ui.notify(`Magnitude setup could not open. ${SETUP_REMINDER}`, "warning")))), scope));
|
|
6192
|
+
});
|
|
6193
|
+
return async () => {
|
|
6194
|
+
abort.abort();
|
|
6195
|
+
await Effect13.runPromise(Scope5.close(scope, Exit4.void));
|
|
6196
|
+
await setup.dispose();
|
|
6197
|
+
};
|
|
6198
|
+
}
|
|
6199
|
+
|
|
5844
6200
|
// extensions/observing-fetch.ts
|
|
5845
|
-
import { Effect as
|
|
6201
|
+
import { Effect as Effect14, Either as Either3, Schema as Schema55 } from "effect";
|
|
5846
6202
|
|
|
5847
6203
|
// extensions/protocol.ts
|
|
5848
|
-
import { Either as Either2, Schema as
|
|
5849
|
-
var ProgressChunkSchema =
|
|
5850
|
-
var TimingsChunkSchema =
|
|
6204
|
+
import { Either as Either2, Schema as Schema54 } from "effect";
|
|
6205
|
+
var ProgressChunkSchema = Schema54.Struct({ progress: MagnitudeProgressSchema });
|
|
6206
|
+
var TimingsChunkSchema = Schema54.Struct({ timings: MagnitudeTimingsSchema });
|
|
5851
6207
|
var decodeMagnitudeObservation = (value) => {
|
|
5852
|
-
const progress =
|
|
5853
|
-
const timings =
|
|
6208
|
+
const progress = Schema54.decodeUnknownEither(ProgressChunkSchema)(value);
|
|
6209
|
+
const timings = Schema54.decodeUnknownEither(TimingsChunkSchema)(value);
|
|
5854
6210
|
if (Either2.isLeft(progress) && Either2.isLeft(timings))
|
|
5855
6211
|
return;
|
|
5856
6212
|
return {
|
|
@@ -5861,7 +6217,7 @@ var decodeMagnitudeObservation = (value) => {
|
|
|
5861
6217
|
|
|
5862
6218
|
// extensions/observing-fetch.ts
|
|
5863
6219
|
var MAGNITUDE_PROGRESS_HEADER = "Magnitude-Include-Progress";
|
|
5864
|
-
var JsonDocumentSchema =
|
|
6220
|
+
var JsonDocumentSchema = Schema55.parseJson(Schema55.Unknown);
|
|
5865
6221
|
|
|
5866
6222
|
class SseDataParser {
|
|
5867
6223
|
#buffer = "";
|
|
@@ -5908,26 +6264,26 @@ class SseDataParser {
|
|
|
5908
6264
|
this.#dataLines = [];
|
|
5909
6265
|
if (data === "[DONE]")
|
|
5910
6266
|
return;
|
|
5911
|
-
const decoded =
|
|
6267
|
+
const decoded = Schema55.decodeUnknownEither(JsonDocumentSchema)(data);
|
|
5912
6268
|
if (Either3.isRight(decoded))
|
|
5913
6269
|
observe(decoded.right);
|
|
5914
6270
|
}
|
|
5915
6271
|
}
|
|
5916
|
-
var observeResponse = (response, request, signal) =>
|
|
6272
|
+
var observeResponse = (response, request, signal) => Effect14.scoped(Effect14.gen(function* () {
|
|
5917
6273
|
if (!response.ok || response.body === null) {
|
|
5918
6274
|
yield* request.fail;
|
|
5919
6275
|
return;
|
|
5920
6276
|
}
|
|
5921
|
-
const reader = yield*
|
|
6277
|
+
const reader = yield* Effect14.acquireRelease(Effect14.sync(() => response.body.getReader()), (reader2) => Effect14.promise(() => reader2.cancel()).pipe(Effect14.interruptible, Effect14.timeout("100 millis"), Effect14.ignore, Effect14.ensuring(Effect14.sync(() => reader2.releaseLock()))));
|
|
5922
6278
|
if (signal.aborted) {
|
|
5923
6279
|
yield* request.fail;
|
|
5924
6280
|
return;
|
|
5925
6281
|
}
|
|
5926
6282
|
const decoder = new TextDecoder;
|
|
5927
6283
|
const parser = new SseDataParser;
|
|
5928
|
-
const drain =
|
|
6284
|
+
const drain = Effect14.gen(function* () {
|
|
5929
6285
|
while (true) {
|
|
5930
|
-
const result = yield*
|
|
6286
|
+
const result = yield* Effect14.tryPromise(() => reader.read());
|
|
5931
6287
|
if (result.done)
|
|
5932
6288
|
break;
|
|
5933
6289
|
const values = [];
|
|
@@ -5940,35 +6296,35 @@ var observeResponse = (response, request, signal) => Effect11.scoped(Effect11.ge
|
|
|
5940
6296
|
}
|
|
5941
6297
|
yield* request.finish;
|
|
5942
6298
|
});
|
|
5943
|
-
const aborted =
|
|
5944
|
-
const abort = () => resume(
|
|
6299
|
+
const aborted = Effect14.async((resume) => {
|
|
6300
|
+
const abort = () => resume(Effect14.interrupt);
|
|
5945
6301
|
signal.addEventListener("abort", abort, { once: true });
|
|
5946
6302
|
if (signal.aborted)
|
|
5947
6303
|
abort();
|
|
5948
|
-
return
|
|
6304
|
+
return Effect14.sync(() => signal.removeEventListener("abort", abort));
|
|
5949
6305
|
});
|
|
5950
|
-
yield*
|
|
5951
|
-
})).pipe(
|
|
6306
|
+
yield* Effect14.raceFirst(drain, aborted).pipe(Effect14.onError(() => request.fail));
|
|
6307
|
+
})).pipe(Effect14.catchAllCause(() => Effect14.void));
|
|
5952
6308
|
var makeObservingFetch = (fetchImplementation, begin, scope) => {
|
|
5953
6309
|
const observingFetch = async (input, init) => {
|
|
5954
6310
|
const request = new Request(input, init);
|
|
5955
6311
|
request.headers.set(MAGNITUDE_PROGRESS_HEADER, "true");
|
|
5956
|
-
const progress =
|
|
5957
|
-
observe: () =>
|
|
5958
|
-
finish:
|
|
5959
|
-
fail:
|
|
6312
|
+
const progress = Effect14.runSync(Effect14.suspend(begin).pipe(Effect14.catchAllCause(() => Effect14.succeed({
|
|
6313
|
+
observe: () => Effect14.void,
|
|
6314
|
+
finish: Effect14.void,
|
|
6315
|
+
fail: Effect14.void
|
|
5960
6316
|
}))));
|
|
5961
6317
|
try {
|
|
5962
6318
|
const response = await fetchImplementation(request);
|
|
5963
6319
|
try {
|
|
5964
6320
|
const observation = observeResponse(response.clone(), progress, request.signal);
|
|
5965
|
-
|
|
6321
|
+
Effect14.runFork(Effect14.forkIn(observation, scope));
|
|
5966
6322
|
} catch {
|
|
5967
|
-
|
|
6323
|
+
Effect14.runSync(progress.fail.pipe(Effect14.catchAllCause(() => Effect14.void)));
|
|
5968
6324
|
}
|
|
5969
6325
|
return response;
|
|
5970
6326
|
} catch (error) {
|
|
5971
|
-
|
|
6327
|
+
Effect14.runSync(progress.fail.pipe(Effect14.catchAllCause(() => Effect14.void)));
|
|
5972
6328
|
throw error;
|
|
5973
6329
|
}
|
|
5974
6330
|
};
|
|
@@ -5979,9 +6335,9 @@ var makeObservingFetch = (fetchImplementation, begin, scope) => {
|
|
|
5979
6335
|
|
|
5980
6336
|
// extensions/progress.ts
|
|
5981
6337
|
import { Text } from "@earendil-works/pi-tui";
|
|
5982
|
-
import { Data as Data3, Effect as
|
|
6338
|
+
import { Data as Data3, Effect as Effect15, Fiber as Fiber3, Option as Option18, Queue, Schema as Schema56 } from "effect";
|
|
5983
6339
|
var MAGNITUDE_SUMMARY_WIDGET_KEY = "magnitude-inference-summary";
|
|
5984
|
-
var RequestId =
|
|
6340
|
+
var RequestId = Schema56.Number.pipe(Schema56.int(), Schema56.brand("ProgressRequestId"));
|
|
5985
6341
|
|
|
5986
6342
|
class Observing extends Data3.TaggedClass("Observing") {
|
|
5987
6343
|
}
|
|
@@ -6036,31 +6392,31 @@ var formatLiveProgress = ({ progress, modelName, startedAt }, now) => {
|
|
|
6036
6392
|
}
|
|
6037
6393
|
}
|
|
6038
6394
|
};
|
|
6039
|
-
var makeProgressTracker = (ui, now = () => performance.now()) =>
|
|
6395
|
+
var makeProgressTracker = (ui, now = () => performance.now()) => Effect15.gen(function* () {
|
|
6040
6396
|
let state = new Idle2;
|
|
6041
6397
|
let nextId = 0;
|
|
6042
6398
|
let active;
|
|
6043
6399
|
let latest;
|
|
6044
6400
|
let nextResponseId = 0;
|
|
6045
6401
|
const wake = yield* Queue.sliding(1);
|
|
6046
|
-
const present = (f) =>
|
|
6402
|
+
const present = (f) => Effect15.sync(f).pipe(Effect15.catchAllCause(() => Effect15.void));
|
|
6047
6403
|
const clearSummary = present(() => ui.setWidget(MAGNITUDE_SUMMARY_WIDGET_KEY, undefined));
|
|
6048
6404
|
const resetRow = present(() => ui.setWorkingMessage());
|
|
6049
6405
|
const render2 = present(() => {
|
|
6050
6406
|
if (active)
|
|
6051
6407
|
ui.setWorkingMessage(formatLiveProgress(active.phase, now()));
|
|
6052
6408
|
});
|
|
6053
|
-
const finalize =
|
|
6409
|
+
const finalize = Effect15.gen(function* () {
|
|
6054
6410
|
if (state._tag !== "Settled")
|
|
6055
6411
|
return;
|
|
6056
|
-
if ([...state.run.responses.values()].some(
|
|
6412
|
+
if ([...state.run.responses.values()].some(Option18.isNone))
|
|
6057
6413
|
return;
|
|
6058
6414
|
if ([...state.run.requests.values()].some((r) => r._tag === "Observing"))
|
|
6059
6415
|
return;
|
|
6060
6416
|
const settled = state;
|
|
6061
6417
|
state = runMachine.transition(settled, "Idle", {});
|
|
6062
6418
|
const timings = [...settled.run.completed.entries()].filter(([id]) => settled.run.accepted.has(id)).sort(([a], [b]) => a - b).map(([, value]) => value);
|
|
6063
|
-
if ([...settled.run.responses.values()].some((outcome) => !
|
|
6419
|
+
if ([...settled.run.responses.values()].some((outcome) => !Option18.getOrElse(outcome, () => false)) || timings.length === 0) {
|
|
6064
6420
|
yield* clearSummary;
|
|
6065
6421
|
return;
|
|
6066
6422
|
}
|
|
@@ -6069,7 +6425,7 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
|
|
|
6069
6425
|
const summary = `● ${settled.run.modelName} worked for ${duration(settled.settledAt - settled.run.startedAt)}` + ` · ${seconds(timings[0].time_to_first_token_ms)} TTFT` + (decodeMs > 0 && tokens > 0 ? ` · ${(tokens * 1000 / decodeMs).toFixed(1)} tok/s` : "");
|
|
6070
6426
|
yield* present(() => ui.setWidget(MAGNITUDE_SUMMARY_WIDGET_KEY, (_tui, theme) => new Text(theme.fg("muted", summary), 0, 0)));
|
|
6071
6427
|
});
|
|
6072
|
-
const clear =
|
|
6428
|
+
const clear = Effect15.gen(function* () {
|
|
6073
6429
|
if (state._tag === "Disposed")
|
|
6074
6430
|
return;
|
|
6075
6431
|
if (state._tag !== "Idle")
|
|
@@ -6079,7 +6435,7 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
|
|
|
6079
6435
|
yield* resetRow;
|
|
6080
6436
|
yield* clearSummary;
|
|
6081
6437
|
});
|
|
6082
|
-
const startRun = (modelName) =>
|
|
6438
|
+
const startRun = (modelName) => Effect15.gen(function* () {
|
|
6083
6439
|
if (state._tag === "Disposed")
|
|
6084
6440
|
return;
|
|
6085
6441
|
if (state._tag === "Settled")
|
|
@@ -6089,38 +6445,38 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
|
|
|
6089
6445
|
state = runMachine.transition(state, "Working", { run: { startedAt: now(), modelName, requests: new Map, completed: new Map, responses: new Map, accepted: new Set } });
|
|
6090
6446
|
yield* clearSummary;
|
|
6091
6447
|
});
|
|
6092
|
-
const timer = yield*
|
|
6448
|
+
const timer = yield* Effect15.forever(Effect15.gen(function* () {
|
|
6093
6449
|
yield* Queue.take(wake);
|
|
6094
6450
|
while (active) {
|
|
6095
|
-
yield*
|
|
6451
|
+
yield* Effect15.sleep("100 millis");
|
|
6096
6452
|
yield* render2;
|
|
6097
6453
|
}
|
|
6098
|
-
})).pipe(
|
|
6099
|
-
yield*
|
|
6454
|
+
})).pipe(Effect15.forkScoped);
|
|
6455
|
+
yield* Effect15.addFinalizer(() => Effect15.gen(function* () {
|
|
6100
6456
|
yield* clear;
|
|
6101
6457
|
if (state._tag !== "Disposed")
|
|
6102
6458
|
state = runMachine.transition(state, "Disposed", {});
|
|
6103
|
-
yield*
|
|
6459
|
+
yield* Fiber3.interrupt(timer);
|
|
6104
6460
|
}));
|
|
6105
6461
|
return {
|
|
6106
6462
|
startRun,
|
|
6107
6463
|
clear,
|
|
6108
|
-
settleRun:
|
|
6464
|
+
settleRun: Effect15.gen(function* () {
|
|
6109
6465
|
if (state._tag === "Working")
|
|
6110
6466
|
state = runMachine.transition(state, "Settled", { settledAt: now() });
|
|
6111
6467
|
yield* finalize;
|
|
6112
6468
|
}),
|
|
6113
|
-
beginResponse: (modelName) =>
|
|
6469
|
+
beginResponse: (modelName) => Effect15.gen(function* () {
|
|
6114
6470
|
yield* startRun(modelName);
|
|
6115
6471
|
const run = state._tag === "Working" ? state.run : undefined;
|
|
6116
6472
|
const responseId = ++nextResponseId;
|
|
6117
6473
|
const requests = [];
|
|
6118
|
-
run?.responses.set(responseId,
|
|
6474
|
+
run?.responses.set(responseId, Option18.none());
|
|
6119
6475
|
const belongs = () => run !== undefined && (state._tag === "Working" || state._tag === "Settled") && state.run === run;
|
|
6120
|
-
const end = (successful) =>
|
|
6121
|
-
if (!belongs() ||
|
|
6476
|
+
const end = (successful) => Effect15.gen(function* () {
|
|
6477
|
+
if (!belongs() || Option18.isSome(run.responses.get(responseId)))
|
|
6122
6478
|
return;
|
|
6123
|
-
run.responses.set(responseId,
|
|
6479
|
+
run.responses.set(responseId, Option18.some(successful));
|
|
6124
6480
|
const last = requests.at(-1);
|
|
6125
6481
|
if (successful && last !== undefined)
|
|
6126
6482
|
run.accepted.add(last);
|
|
@@ -6139,16 +6495,16 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
|
|
|
6139
6495
|
}
|
|
6140
6496
|
yield* finalize;
|
|
6141
6497
|
});
|
|
6142
|
-
const begin =
|
|
6143
|
-
if (!belongs() ||
|
|
6144
|
-
return { observe: () =>
|
|
6498
|
+
const begin = Effect15.gen(function* () {
|
|
6499
|
+
if (!belongs() || Option18.isSome(run.responses.get(responseId)))
|
|
6500
|
+
return { observe: () => Effect15.void, finish: Effect15.void, fail: Effect15.void };
|
|
6145
6501
|
const id = RequestId.make(++nextId);
|
|
6146
6502
|
requests.push(id);
|
|
6147
6503
|
latest = id;
|
|
6148
6504
|
active = undefined;
|
|
6149
|
-
run?.requests.set(id, new Observing({ timings:
|
|
6505
|
+
run?.requests.set(id, new Observing({ timings: Option18.none() }));
|
|
6150
6506
|
yield* resetRow;
|
|
6151
|
-
const close = (failed) =>
|
|
6507
|
+
const close = (failed) => Effect15.gen(function* () {
|
|
6152
6508
|
if (!belongs())
|
|
6153
6509
|
return;
|
|
6154
6510
|
const request = run.requests.get(id);
|
|
@@ -6158,7 +6514,7 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
|
|
|
6158
6514
|
run.requests.set(id, requestMachine.transition(request, "Closed", {}));
|
|
6159
6515
|
else {
|
|
6160
6516
|
run.requests.set(id, requestMachine.transition(request, "Observed", {}));
|
|
6161
|
-
if (
|
|
6517
|
+
if (Option18.isSome(request.timings))
|
|
6162
6518
|
run.completed.set(id, request.timings.value);
|
|
6163
6519
|
}
|
|
6164
6520
|
if (latest === id) {
|
|
@@ -6168,14 +6524,14 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
|
|
|
6168
6524
|
yield* finalize;
|
|
6169
6525
|
});
|
|
6170
6526
|
return {
|
|
6171
|
-
observe: (observation) =>
|
|
6527
|
+
observe: (observation) => Effect15.gen(function* () {
|
|
6172
6528
|
if (!belongs())
|
|
6173
6529
|
return;
|
|
6174
6530
|
const request = run.requests.get(id);
|
|
6175
6531
|
if (request._tag !== "Observing")
|
|
6176
6532
|
return;
|
|
6177
6533
|
if (observation.timings)
|
|
6178
|
-
run.requests.set(id, requestMachine.hold(request, { timings:
|
|
6534
|
+
run.requests.set(id, requestMachine.hold(request, { timings: Option18.some(observation.timings) }));
|
|
6179
6535
|
if (latest !== id || !observation.progress)
|
|
6180
6536
|
return;
|
|
6181
6537
|
const progress = observation.progress;
|
|
@@ -6199,34 +6555,35 @@ var makeProgressTracker = (ui, now = () => performance.now()) => Effect12.gen(fu
|
|
|
6199
6555
|
});
|
|
6200
6556
|
|
|
6201
6557
|
// extensions/magnitude.ts
|
|
6202
|
-
import { Effect as
|
|
6558
|
+
import { Effect as Effect16, Exit as Exit5, Scope as Scope7 } from "effect";
|
|
6203
6559
|
function magnitudeExtension(pi) {
|
|
6204
6560
|
const completions = openAICompletionsApi();
|
|
6205
6561
|
let tracker;
|
|
6206
6562
|
let scope;
|
|
6207
6563
|
const disposeCommands = registerMagnitudeCommands(pi);
|
|
6208
|
-
const
|
|
6564
|
+
const disposeOnboarding = registerMagnitudeOnboarding(pi);
|
|
6565
|
+
const perform = (effect) => Effect16.runSync((effect ?? Effect16.void).pipe(Effect16.catchAllCause(() => Effect16.void)));
|
|
6209
6566
|
pi.registerProvider("magnitude", {
|
|
6210
6567
|
api: "openai-completions",
|
|
6211
6568
|
streamSimple: (model2, context, options) => {
|
|
6212
6569
|
if (model2.api !== "openai-completions") {
|
|
6213
6570
|
throw new Error(`Magnitude for Pi requires openai-completions, received ${model2.api}`);
|
|
6214
6571
|
}
|
|
6215
|
-
const response = tracker &&
|
|
6572
|
+
const response = tracker && Effect16.runSync(tracker.beginResponse(model2.name));
|
|
6216
6573
|
const stream = completions.streamSimple(model2, context, {
|
|
6217
6574
|
...options,
|
|
6218
|
-
fetch: scope ? makeObservingFetch(options?.fetch ?? globalThis.fetch, () => response?.begin ??
|
|
6575
|
+
fetch: scope ? makeObservingFetch(options?.fetch ?? globalThis.fetch, () => response?.begin ?? Effect16.succeed({ observe: () => Effect16.void, finish: Effect16.void, fail: Effect16.void }), scope) : options?.fetch ?? globalThis.fetch
|
|
6219
6576
|
});
|
|
6220
6577
|
if (response && scope)
|
|
6221
|
-
|
|
6578
|
+
Effect16.runFork(Effect16.forkIn(Effect16.tryPromise(() => stream.result()).pipe(Effect16.flatMap((message) => response.end(message.stopReason !== "error" && message.stopReason !== "aborted")), Effect16.catchAllCause(() => response.end(false))), scope));
|
|
6222
6579
|
return stream;
|
|
6223
6580
|
}
|
|
6224
6581
|
});
|
|
6225
6582
|
pi.on("session_start", async (_event, ctx) => {
|
|
6226
6583
|
if (scope)
|
|
6227
|
-
await
|
|
6228
|
-
scope =
|
|
6229
|
-
tracker = await
|
|
6584
|
+
await Effect16.runPromise(Scope7.close(scope, Exit5.void));
|
|
6585
|
+
scope = Effect16.runSync(Scope7.make());
|
|
6586
|
+
tracker = await Effect16.runPromise(makeProgressTracker(ctx.ui).pipe(Scope7.extend(scope)));
|
|
6230
6587
|
});
|
|
6231
6588
|
pi.on("model_select", (event) => {
|
|
6232
6589
|
if (event.model.provider !== "magnitude")
|
|
@@ -6238,8 +6595,9 @@ function magnitudeExtension(pi) {
|
|
|
6238
6595
|
});
|
|
6239
6596
|
pi.on("agent_settled", () => perform(tracker?.settleRun));
|
|
6240
6597
|
pi.on("session_shutdown", async () => {
|
|
6598
|
+
await disposeOnboarding();
|
|
6241
6599
|
if (scope)
|
|
6242
|
-
await
|
|
6600
|
+
await Effect16.runPromise(Scope7.close(scope, Exit5.void));
|
|
6243
6601
|
tracker = undefined;
|
|
6244
6602
|
scope = undefined;
|
|
6245
6603
|
await disposeCommands();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: magnitude
|
|
3
|
+
description: Set up and operate Magnitude local inference through its CLI, recommend hardware-fit local models, monitor acquisition and loading, and connect an agent harness. Use for Magnitude service, model, catalog, setup, or harness requests.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Magnitude
|
|
7
|
+
|
|
8
|
+
Magnitude profiles the local machine, assesses compatible model configurations, acquires and runs
|
|
9
|
+
selected models, and connects them to supported agent harnesses.
|
|
10
|
+
|
|
11
|
+
For agent-guided setup, read `magnitude docs onboarding` completely and follow it. It contains the
|
|
12
|
+
current CLI-only workflow, model-selection evidence, progress semantics, and harness connection
|
|
13
|
+
rules.
|
|
14
|
+
|
|
15
|
+
For the general non-interactive command contract, read `magnitude docs cli`. Command output is
|
|
16
|
+
designed to be read directly by both agents and people.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@magnitudedev/pi-extension",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.2",
|
|
4
4
|
"description": "Magnitude model controls and inference progress for Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"@earendil-works/pi-tui": ">=0.83.0"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
|
+
"@magnitudedev/client-common": "workspace:*",
|
|
31
32
|
"@effect/platform-bun": "^0.90.0",
|
|
32
33
|
"@magnitudedev/release": "workspace:*",
|
|
33
34
|
"@magnitudedev/utils": "workspace:*",
|