@ycodium-ai/agent-pi 0.2.3576 → 0.2.3608
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -5
- package/dist/index.js +56 -21
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -21,8 +21,8 @@ in `/v1`, without `/chat/completions` or `/responses`. For Anthropic Messages,
|
|
|
21
21
|
supply the service root or a base URL ending in `/v1`; the runtime resolves the
|
|
22
22
|
request route to `/v1/messages`. URLs must not contain embedded credentials,
|
|
23
23
|
query parameters or a fragment. The model ID must be accepted by that service. The instance exposes
|
|
24
|
-
|
|
25
|
-
verify API credentials. The first request checks access to the service.
|
|
24
|
+
the configured model plus the instance's custom models, all on the same endpoint and key;
|
|
25
|
+
its readiness check does not make a model request or verify API credentials. The first request checks access to the service.
|
|
26
26
|
|
|
27
27
|
## Install
|
|
28
28
|
|
|
@@ -48,7 +48,8 @@ is shown as invalid and must be explicitly replaced. Advanced configurations
|
|
|
48
48
|
can also supply `YCODIUM_PI_API_BASE_URL`.
|
|
49
49
|
|
|
50
50
|
The plugin starts `pi --mode rpc` through Ycodium's managed process API. It
|
|
51
|
-
provides temporary model and MCP configuration
|
|
51
|
+
provides temporary model and MCP configuration (every listed model under one `ycodium`
|
|
52
|
+
provider), and enables Pi's built-in MCP and tool-search
|
|
52
53
|
extensions. MCP tools are discovered on demand rather than all advertised in the first request.
|
|
53
54
|
Native session files live in a directory specific
|
|
54
55
|
to the instance under `~/.pi/ycodium/`, unless a session directory is configured
|
|
@@ -60,9 +61,11 @@ history and require a new thread. Missing saved history is reported as an error;
|
|
|
60
61
|
does not silently replace it with an empty conversation.
|
|
61
62
|
|
|
62
63
|
Pi runs with full process permissions. It provides no sandbox or native per-tool
|
|
63
|
-
approval flow. Context compaction and steering use native RPC operations.
|
|
64
|
+
approval flow. Context compaction and steering use native RPC operations. Choosing another listed model
|
|
65
|
+
sends `set_model` before the next prompt in the same process; steering keeps the current model.
|
|
66
|
+
Models are declared without reasoning support, so no thinking-level option is exposed. This
|
|
64
67
|
integration does not offer OAuth, an auxiliary text-generation assistant,
|
|
65
|
-
|
|
68
|
+
inline image attachments, or a guarantee that an
|
|
66
69
|
interrupted tool's external side effects have stopped. MCP transports are stdio
|
|
67
70
|
and HTTP; legacy SSE is unsupported.
|
|
68
71
|
|
package/dist/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { definePlugin } from "@ycodium-ai/plugin-api";
|
|
|
5
5
|
import * as NodeCrypto from "node:crypto";
|
|
6
6
|
import * as NodeOS from "node:os";
|
|
7
7
|
import * as NodePath from "node:path";
|
|
8
|
+
import { matchCatalogModel } from "@ycodium-ai/plugin-api/executor";
|
|
8
9
|
|
|
9
10
|
// src/protocol.ts
|
|
10
11
|
var PI_API_PROTOCOLS = [
|
|
@@ -37,11 +38,16 @@ function parseConfig(value, instanceId) {
|
|
|
37
38
|
);
|
|
38
39
|
const configuredHome = string(config.homePath).trim();
|
|
39
40
|
const homePath = configuredHome.startsWith("~/") || configuredHome.startsWith("~\\") ? NodePath.join(NodeOS.homedir(), configuredHome.slice(2)) : configuredHome;
|
|
41
|
+
const model = string(config.model).trim();
|
|
42
|
+
const custom = (Array.isArray(config.customModels) ? config.customModels : []).map(
|
|
43
|
+
(entry) => string(typeof entry === "string" ? entry : record(entry).slug).trim()
|
|
44
|
+
);
|
|
40
45
|
return {
|
|
41
46
|
binaryPath: string(config.binaryPath).trim() || "pi",
|
|
42
47
|
apiBaseUrl: string(config.apiBaseUrl).trim() || environment.YCODIUM_PI_API_BASE_URL?.trim() || "",
|
|
43
48
|
apiProtocol: readPiApiProtocol(config.apiProtocol),
|
|
44
|
-
model
|
|
49
|
+
model,
|
|
50
|
+
models: [...new Set([model, ...custom].filter(Boolean))],
|
|
45
51
|
environment,
|
|
46
52
|
homePath: homePath ? NodePath.resolve(homePath) : NodePath.join(NodeOS.homedir(), ".pi", "ycodium", identifier(instanceId))
|
|
47
53
|
};
|
|
@@ -65,6 +71,18 @@ function validateConnection(config) {
|
|
|
65
71
|
if (!config.model) throw new Error("Pi model ID is required.");
|
|
66
72
|
if (!config.environment[PI_API_KEY_ENV]?.trim()) throw new Error("Pi API key is required.");
|
|
67
73
|
}
|
|
74
|
+
function requestedModel(config, selection) {
|
|
75
|
+
const raw = string(record(selection).model).trim();
|
|
76
|
+
if (!raw) return void 0;
|
|
77
|
+
const id = matchCatalogModel(raw, config.models) ?? matchCatalogModel(raw.replace(/^ycodium\//, ""), config.models);
|
|
78
|
+
if (id) return id;
|
|
79
|
+
throw Object.assign(
|
|
80
|
+
new Error(
|
|
81
|
+
`Pi instance does not list model "${raw}". Choose one of: ${config.models.join(", ")}. Provider setting changes apply after the session restarts.`
|
|
82
|
+
),
|
|
83
|
+
{ code: "provider-rejected" }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
68
86
|
function redact(config, value) {
|
|
69
87
|
let text = value instanceof Error ? value.message : String(value);
|
|
70
88
|
for (const secret of Object.values(config.environment).filter(Boolean).sort((a, b) => b.length - a.length))
|
|
@@ -97,7 +115,7 @@ var PI_CARD = {
|
|
|
97
115
|
verifiedSkills: false,
|
|
98
116
|
everydayHarness: false,
|
|
99
117
|
capabilities: {
|
|
100
|
-
sessionModelSwitch: "
|
|
118
|
+
sessionModelSwitch: "in-session",
|
|
101
119
|
runtimeModeSwitch: "next-turn",
|
|
102
120
|
contextCompaction: "native",
|
|
103
121
|
turnSteering: true
|
|
@@ -147,7 +165,8 @@ import {
|
|
|
147
165
|
describeExecutorSpawnFailure,
|
|
148
166
|
ExecutorSpawnAbortedError,
|
|
149
167
|
ExecutorSpawnRefusedError,
|
|
150
|
-
isExecutorSpawnFailure
|
|
168
|
+
isExecutorSpawnFailure,
|
|
169
|
+
raceAbort
|
|
151
170
|
} from "@ycodium-ai/plugin-api/executor";
|
|
152
171
|
import { runProcessOperation } from "@ycodium-ai/plugin-api/process-operation";
|
|
153
172
|
|
|
@@ -349,7 +368,7 @@ async function createLaunch(config, input, cursor) {
|
|
|
349
368
|
baseUrl,
|
|
350
369
|
api: apiProtocol,
|
|
351
370
|
apiKey: `\${${PI_API_KEY_ENV}}`,
|
|
352
|
-
models:
|
|
371
|
+
models: config.models.map((id) => ({ id }))
|
|
353
372
|
}
|
|
354
373
|
}
|
|
355
374
|
}),
|
|
@@ -381,7 +400,7 @@ async function createLaunch(config, input, cursor) {
|
|
|
381
400
|
"rpc",
|
|
382
401
|
"--no-approve",
|
|
383
402
|
"--model",
|
|
384
|
-
`ycodium/${config.model}`,
|
|
403
|
+
`ycodium/${requestedModel(config, input?.modelSelection) ?? config.model}`,
|
|
385
404
|
"--no-extensions",
|
|
386
405
|
"--no-skills"
|
|
387
406
|
];
|
|
@@ -862,13 +881,6 @@ function createPiInstance(value, host) {
|
|
|
862
881
|
const disposed = new AbortController();
|
|
863
882
|
const now = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
864
883
|
const nativeMode = PI_NATIVE_MODE_REPORT;
|
|
865
|
-
const validateModel = (selection) => {
|
|
866
|
-
const model = string(record(selection).model).trim();
|
|
867
|
-
if (model && model !== config.model && model !== `ycodium/${config.model}`)
|
|
868
|
-
throw Object.assign(new Error("Pi can only run the model configured for this instance."), {
|
|
869
|
-
code: "provider-rejected"
|
|
870
|
-
});
|
|
871
|
-
};
|
|
872
884
|
const safeError = (cause) => {
|
|
873
885
|
if (cause instanceof ExecutorSpawnRefusedError || cause instanceof ExecutorSpawnAbortedError)
|
|
874
886
|
return cause;
|
|
@@ -992,6 +1004,8 @@ function createPiInstance(value, host) {
|
|
|
992
1004
|
const abortTurn = async (booked, signal) => {
|
|
993
1005
|
const turn = booked.turn, conn = booked.connection;
|
|
994
1006
|
if (!turn || turn.completed || !conn) return;
|
|
1007
|
+
if (booked.dispatching) await raceAbort(booked.dispatching, signal);
|
|
1008
|
+
if (turn.completed) return;
|
|
995
1009
|
await conn.rpc.request("clear_queue", {}, signal, 1e4);
|
|
996
1010
|
await conn.rpc.request("abort", {}, signal, 1e4);
|
|
997
1011
|
await waitForSettled(booked, turn, signal);
|
|
@@ -1019,7 +1033,7 @@ function createPiInstance(value, host) {
|
|
|
1019
1033
|
const startOne = async (input, signal) => {
|
|
1020
1034
|
ensureActive();
|
|
1021
1035
|
validateConnection(config);
|
|
1022
|
-
|
|
1036
|
+
const model = requestedModel(config, input.modelSelection) ?? config.model;
|
|
1023
1037
|
signal?.throwIfAborted();
|
|
1024
1038
|
const nativeModeId = piNativeModeFor(input.runtimeMode, input.nativeModeId);
|
|
1025
1039
|
const existing = threads.get(input.threadId);
|
|
@@ -1037,7 +1051,7 @@ function createPiInstance(value, host) {
|
|
|
1037
1051
|
runtimeMode: input.runtimeMode,
|
|
1038
1052
|
nativeModeId,
|
|
1039
1053
|
status: "starting",
|
|
1040
|
-
model
|
|
1054
|
+
model,
|
|
1041
1055
|
createdAt: now(),
|
|
1042
1056
|
updatedAt: now(),
|
|
1043
1057
|
resumeCursor: cursor,
|
|
@@ -1199,10 +1213,13 @@ function createPiInstance(value, host) {
|
|
|
1199
1213
|
startSession: start,
|
|
1200
1214
|
sendTurn: async (input, signal) => {
|
|
1201
1215
|
signal?.throwIfAborted();
|
|
1202
|
-
|
|
1216
|
+
const requested = requestedModel(config, input.modelSelection);
|
|
1203
1217
|
const booked = requireThread(input.threadId), conn = connection(booked);
|
|
1204
1218
|
if (booked.stopping) throw new Error("Pi is stopping the current turn.");
|
|
1205
1219
|
if (input.expectedTurnId !== void 0) {
|
|
1220
|
+
if (booked.dispatching) await raceAbort(booked.dispatching, signal);
|
|
1221
|
+
signal?.throwIfAborted();
|
|
1222
|
+
if (booked.stopping || booked.closing) throw new Error("Pi is stopping the current turn.");
|
|
1206
1223
|
if (!booked.turn || booked.turn.id !== input.expectedTurnId || booked.turn.completed)
|
|
1207
1224
|
throw Object.assign(new Error("The target Pi turn has ended."), {
|
|
1208
1225
|
code: "provider-rejected"
|
|
@@ -1221,7 +1238,8 @@ function createPiInstance(value, host) {
|
|
|
1221
1238
|
}
|
|
1222
1239
|
if (booked.turn) throw new Error("Pi already has an active turn.");
|
|
1223
1240
|
const cursor = record(booked.session.resumeCursor);
|
|
1224
|
-
const
|
|
1241
|
+
const model = requested ?? booked.session.model ?? config.model;
|
|
1242
|
+
const turn = new PiTurn(booked.emit, model, string(cursor.sessionId), () => {
|
|
1225
1243
|
if (booked.turn === turn) booked.turn = void 0;
|
|
1226
1244
|
booked.session.status = "idle";
|
|
1227
1245
|
booked.session.updatedAt = now();
|
|
@@ -1229,11 +1247,26 @@ function createPiInstance(value, host) {
|
|
|
1229
1247
|
booked.turn = turn;
|
|
1230
1248
|
booked.session.status = "running";
|
|
1231
1249
|
turn.start();
|
|
1250
|
+
const issued = Promise.withResolvers();
|
|
1251
|
+
const dispatch = (async () => {
|
|
1252
|
+
try {
|
|
1253
|
+
if (model !== booked.session.model) {
|
|
1254
|
+
await conn.rpc.request("set_model", { provider: "ycodium", modelId: model }, signal);
|
|
1255
|
+
booked.session.model = model;
|
|
1256
|
+
}
|
|
1257
|
+
if (booked.stopping || booked.closing) return turn.finish("interrupted");
|
|
1258
|
+
const prompting = conn.rpc.request("prompt", { message: input.input ?? "" }, signal);
|
|
1259
|
+
issued.resolve();
|
|
1260
|
+
const response = record(await prompting);
|
|
1261
|
+
if (response.disposition === "handled") turn.finish("completed");
|
|
1262
|
+
} finally {
|
|
1263
|
+
issued.resolve();
|
|
1264
|
+
}
|
|
1265
|
+
})();
|
|
1266
|
+
const dispatched = issued.promise;
|
|
1267
|
+
booked.dispatching = dispatched;
|
|
1232
1268
|
try {
|
|
1233
|
-
|
|
1234
|
-
await conn.rpc.request("prompt", { message: input.input ?? "" }, signal)
|
|
1235
|
-
);
|
|
1236
|
-
if (response.disposition === "handled") turn.finish("completed");
|
|
1269
|
+
await dispatch;
|
|
1237
1270
|
return {
|
|
1238
1271
|
threadId: input.threadId,
|
|
1239
1272
|
turnId: turn.id,
|
|
@@ -1243,6 +1276,8 @@ function createPiInstance(value, host) {
|
|
|
1243
1276
|
turn.finish("failed", redact(config, cause));
|
|
1244
1277
|
if (!(cause instanceof PiResponseError)) await close(booked);
|
|
1245
1278
|
throw safeError(cause);
|
|
1279
|
+
} finally {
|
|
1280
|
+
if (booked.dispatching === dispatched) booked.dispatching = void 0;
|
|
1246
1281
|
}
|
|
1247
1282
|
},
|
|
1248
1283
|
interruptTurn: async (threadId, turnId, signal) => {
|
|
@@ -1375,7 +1410,7 @@ function createPiInstance(value, host) {
|
|
|
1375
1410
|
// src/index.ts
|
|
1376
1411
|
var index_default = definePlugin({
|
|
1377
1412
|
id: "agent-pi",
|
|
1378
|
-
apiVersion:
|
|
1413
|
+
apiVersion: 49,
|
|
1379
1414
|
executors: [
|
|
1380
1415
|
{ card: PI_CARD, createInstance: async (config, host) => createPiInstance(config, host) }
|
|
1381
1416
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ycodium-ai/agent-pi",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3608",
|
|
4
4
|
"description": "Pi executor over the native RPC protocol.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ycodium",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"tailwindcss": "^4.0.0",
|
|
31
31
|
"typescript": "~6.0.3",
|
|
32
32
|
"vite-plus": "0.2.2",
|
|
33
|
-
"@ycodium-ai/plugin-api": "0.2.
|
|
33
|
+
"@ycodium-ai/plugin-api": "0.2.3608",
|
|
34
34
|
"@ycodium/contracts": "0.0.33",
|
|
35
35
|
"ycodium-executor-testing": "0.0.0"
|
|
36
36
|
},
|