@ai-sdk/harness-pi 1.0.120 → 1.0.121
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/CHANGELOG.md +9 -0
- package/README.md +19 -0
- package/dist/index.d.ts +17 -2
- package/dist/index.js +32 -13
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +1 -1
- package/src/pi-auth.ts +11 -5
- package/src/pi-harness.ts +21 -1
- package/src/pi-session.ts +67 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-sdk/harness-pi",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.121",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ai-sdk/harness": "1.0.
|
|
29
|
+
"@ai-sdk/harness": "1.0.119",
|
|
30
30
|
"@ai-sdk/provider-utils": "5.0.45",
|
|
31
31
|
"@earendil-works/pi-ai": "0.74.2",
|
|
32
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
32
|
+
"@earendil-works/pi-coding-agent": "^0.85.1",
|
|
33
33
|
"pi-mcp-adapter": "2.12.1",
|
|
34
34
|
"typebox": "^1.1.38"
|
|
35
35
|
},
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"zod": "^3.25.76 || ^4.1.8"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@ai-sdk/sandbox-just-bash": "1.0.
|
|
40
|
+
"@ai-sdk/sandbox-just-bash": "1.0.119",
|
|
41
41
|
"@types/node": "22.19.19",
|
|
42
42
|
"@vercel/ai-tsconfig": "0.0.0",
|
|
43
43
|
"tsup": "^8.5.1",
|
package/src/index.ts
CHANGED
|
@@ -9,4 +9,4 @@ export const pi = createPi();
|
|
|
9
9
|
export { createPi } from './pi-harness';
|
|
10
10
|
export { VERSION } from './version';
|
|
11
11
|
export type { PiHarnessSettings } from './pi-harness';
|
|
12
|
-
export type { PiAuthenticationMode } from './pi-auth';
|
|
12
|
+
export type { PiAuthenticationMode, PiCredentialStore } from './pi-auth';
|
package/src/pi-auth.ts
CHANGED
|
@@ -12,7 +12,10 @@ import { access } from 'node:fs/promises';
|
|
|
12
12
|
import { VERSION } from './version';
|
|
13
13
|
|
|
14
14
|
type ProviderConfigInput = Parameters<ModelRegistry['registerProvider']>[1];
|
|
15
|
-
|
|
15
|
+
/** Persistent credential storage consumed by Pi's model runtime. */
|
|
16
|
+
export type PiCredentialStore = NonNullable<
|
|
17
|
+
CreateModelRuntimeOptions['credentials']
|
|
18
|
+
>;
|
|
16
19
|
type PiCredential = Exclude<
|
|
17
20
|
Awaited<ReturnType<PiCredentialStore['read']>>,
|
|
18
21
|
undefined
|
|
@@ -116,24 +119,27 @@ function scopePiProviderEnvironment({
|
|
|
116
119
|
|
|
117
120
|
export async function createPiModelRuntime({
|
|
118
121
|
auth,
|
|
122
|
+
credentials,
|
|
119
123
|
authPath,
|
|
120
124
|
modelsPath,
|
|
121
125
|
}: {
|
|
122
126
|
auth: PiAuthenticationMode | undefined;
|
|
127
|
+
credentials?: PiCredentialStore;
|
|
123
128
|
authPath: string;
|
|
124
129
|
modelsPath: string;
|
|
125
130
|
}): Promise<ModelRuntime> {
|
|
126
131
|
if (!isHarnessAuthenticationEnvironment(auth)) {
|
|
127
132
|
return ModelRuntime.create({
|
|
128
|
-
authPath,
|
|
133
|
+
...(credentials ? { credentials } : { authPath }),
|
|
129
134
|
modelsPath,
|
|
130
135
|
allowModelNetwork: false,
|
|
131
136
|
});
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
const isolatedCredentials =
|
|
139
|
+
const isolatedCredentials =
|
|
140
|
+
credentials == null ? createIsolatedPiCredentialStore() : undefined;
|
|
135
141
|
const modelRuntime = await ModelRuntime.create({
|
|
136
|
-
credentials: isolatedCredentials
|
|
142
|
+
credentials: credentials ?? isolatedCredentials!.credentials,
|
|
137
143
|
modelsPath: null,
|
|
138
144
|
allowModelNetwork: false,
|
|
139
145
|
});
|
|
@@ -164,7 +170,7 @@ export async function createPiModelRuntime({
|
|
|
164
170
|
modelRuntime,
|
|
165
171
|
authenticationEnvironment: auth,
|
|
166
172
|
});
|
|
167
|
-
isolatedCredentials
|
|
173
|
+
isolatedCredentials?.finishInitialization();
|
|
168
174
|
await modelRuntime.refresh({ allowNetwork: false });
|
|
169
175
|
|
|
170
176
|
return modelRuntime;
|
package/src/pi-harness.ts
CHANGED
|
@@ -9,7 +9,7 @@ import type {
|
|
|
9
9
|
ProviderConfig,
|
|
10
10
|
} from '@earendil-works/pi-coding-agent';
|
|
11
11
|
import { z } from 'zod/v4';
|
|
12
|
-
import type { PiAuthenticationMode } from './pi-auth';
|
|
12
|
+
import type { PiAuthenticationMode, PiCredentialStore } from './pi-auth';
|
|
13
13
|
import { piResumeStateSchema } from './pi-resume-state';
|
|
14
14
|
import { createPiSession, type PiThinkingLevel } from './pi-session';
|
|
15
15
|
import { VERSION } from './version';
|
|
@@ -26,6 +26,19 @@ const PI_CLIENT_APP = `ai-sdk/harness-pi/${VERSION}`;
|
|
|
26
26
|
export type PiHarnessSettings = {
|
|
27
27
|
/** Where Pi sources API keys / gateway credentials from. */
|
|
28
28
|
readonly auth?: PiAuthenticationMode;
|
|
29
|
+
/**
|
|
30
|
+
* Application-owned credential storage for Pi's model runtime. When set,
|
|
31
|
+
* this replaces Pi's file-backed auth.json credential storage.
|
|
32
|
+
*/
|
|
33
|
+
readonly credentials?: PiCredentialStore;
|
|
34
|
+
/**
|
|
35
|
+
* Whether a suspended turn may reuse its live Pi session in this process.
|
|
36
|
+
* Disable this in stateless or multi-replica applications so every request
|
|
37
|
+
* restores from persisted lifecycle state with the current settings.
|
|
38
|
+
*
|
|
39
|
+
* @default true
|
|
40
|
+
*/
|
|
41
|
+
readonly reattachInProcess?: boolean;
|
|
29
42
|
/**
|
|
30
43
|
* Explicit Pi provider configurations keyed by provider id. Use this to
|
|
31
44
|
* register custom models and their API protocol without coupling model
|
|
@@ -154,6 +167,12 @@ export function createPi(
|
|
|
154
167
|
sessionWorkDir: startOpts.sessionWorkDir,
|
|
155
168
|
settings: {
|
|
156
169
|
...(settings.auth ? { auth: settings.auth } : {}),
|
|
170
|
+
...(settings.credentials
|
|
171
|
+
? { credentials: settings.credentials }
|
|
172
|
+
: {}),
|
|
173
|
+
...(settings.reattachInProcess != null
|
|
174
|
+
? { reattachInProcess: settings.reattachInProcess }
|
|
175
|
+
: {}),
|
|
157
176
|
...(settings.thinkingLevel
|
|
158
177
|
? { thinkingLevel: settings.thinkingLevel }
|
|
159
178
|
: {}),
|
|
@@ -166,6 +185,7 @@ export function createPi(
|
|
|
166
185
|
},
|
|
167
186
|
clientApp: PI_CLIENT_APP,
|
|
168
187
|
isResume: lifecycleState != null,
|
|
188
|
+
...(lifecycleState ? { resumeStateType: lifecycleState.type } : {}),
|
|
169
189
|
permissionMode: startOpts.permissionMode,
|
|
170
190
|
builtinToolFiltering: startOpts.builtinToolFiltering,
|
|
171
191
|
...(resumeData?.sessionFileName
|
package/src/pi-session.ts
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
registerPiProviders,
|
|
44
44
|
resolvePiEnv,
|
|
45
45
|
type PiAuthenticationMode,
|
|
46
|
+
type PiCredentialStore,
|
|
46
47
|
} from './pi-auth';
|
|
47
48
|
import { resolvePiSubscriptionAgentDir } from './pi-subscription';
|
|
48
49
|
import { getPiTerminalError, parseNativeEvent } from './pi-events';
|
|
@@ -100,7 +101,14 @@ type PiMcpAdapterModule = {
|
|
|
100
101
|
* same-process resume instead of stopping it and resolving the promise as an
|
|
101
102
|
* error. Cross-process resume still falls back to the persisted session file.
|
|
102
103
|
*/
|
|
103
|
-
const parkedPiSessions = new Map<
|
|
104
|
+
const parkedPiSessions = new Map<
|
|
105
|
+
string,
|
|
106
|
+
{
|
|
107
|
+
session: HarnessV1Session;
|
|
108
|
+
input: CreatePiSessionInput;
|
|
109
|
+
stateType: 'continue-turn' | 'resume-session';
|
|
110
|
+
}
|
|
111
|
+
>();
|
|
104
112
|
|
|
105
113
|
/**
|
|
106
114
|
* Whether a discovered resource path belongs to a specific directory.
|
|
@@ -221,6 +229,8 @@ export type PiThinkingLevel =
|
|
|
221
229
|
|
|
222
230
|
export interface PiSessionSettings {
|
|
223
231
|
readonly auth?: PiAuthenticationMode;
|
|
232
|
+
readonly credentials?: PiCredentialStore;
|
|
233
|
+
readonly reattachInProcess?: boolean;
|
|
224
234
|
readonly headers?: Readonly<Record<string, string>>;
|
|
225
235
|
readonly thinkingLevel?: PiThinkingLevel;
|
|
226
236
|
readonly mcpServers?: Record<string, unknown>;
|
|
@@ -235,6 +245,9 @@ export interface CreatePiSessionInput {
|
|
|
235
245
|
readonly settings: PiSessionSettings;
|
|
236
246
|
readonly clientApp: string;
|
|
237
247
|
readonly isResume: boolean;
|
|
248
|
+
readonly resumeStateType?:
|
|
249
|
+
| HarnessV1ContinueTurnState['type']
|
|
250
|
+
| HarnessV1ResumeSessionState['type'];
|
|
238
251
|
readonly permissionMode?: HarnessV1PermissionMode;
|
|
239
252
|
readonly builtinToolFiltering?: HarnessV1BuiltinToolFiltering;
|
|
240
253
|
readonly resumeSessionFileName?: string;
|
|
@@ -248,6 +261,28 @@ export interface CreatePiSessionInput {
|
|
|
248
261
|
readonly agentDir?: string;
|
|
249
262
|
}
|
|
250
263
|
|
|
264
|
+
function hasCompatibleReattachSettings(
|
|
265
|
+
parked: CreatePiSessionInput,
|
|
266
|
+
current: CreatePiSessionInput,
|
|
267
|
+
): boolean {
|
|
268
|
+
return (
|
|
269
|
+
parked.sandboxSession === current.sandboxSession &&
|
|
270
|
+
parked.sessionWorkDir === current.sessionWorkDir &&
|
|
271
|
+
parked.clientApp === current.clientApp &&
|
|
272
|
+
parked.permissionMode === current.permissionMode &&
|
|
273
|
+
parked.builtinToolFiltering === current.builtinToolFiltering &&
|
|
274
|
+
parked.abortSignal === current.abortSignal &&
|
|
275
|
+
parked.agentDir === current.agentDir &&
|
|
276
|
+
parked.settings.auth === current.settings.auth &&
|
|
277
|
+
parked.settings.credentials === current.settings.credentials &&
|
|
278
|
+
parked.settings.headers === current.settings.headers &&
|
|
279
|
+
parked.settings.thinkingLevel === current.settings.thinkingLevel &&
|
|
280
|
+
parked.settings.mcpServers === current.settings.mcpServers &&
|
|
281
|
+
parked.settings.providers === current.settings.providers &&
|
|
282
|
+
parked.settings.extensionFactories === current.settings.extensionFactories
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
|
|
251
286
|
interface PendingToolResult {
|
|
252
287
|
resolve: (value: unknown) => void;
|
|
253
288
|
}
|
|
@@ -319,13 +354,27 @@ export async function createPiSession(
|
|
|
319
354
|
input: CreatePiSessionInput,
|
|
320
355
|
): Promise<HarnessV1Session> {
|
|
321
356
|
if (input.isResume) {
|
|
322
|
-
const
|
|
323
|
-
if (
|
|
357
|
+
const parked = parkedPiSessions.get(input.sessionId);
|
|
358
|
+
if (parked) {
|
|
324
359
|
parkedPiSessions.delete(input.sessionId);
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
360
|
+
if (
|
|
361
|
+
input.settings.reattachInProcess !== false &&
|
|
362
|
+
(input.resumeStateType == null ||
|
|
363
|
+
input.resumeStateType === parked.stateType) &&
|
|
364
|
+
hasCompatibleReattachSettings(parked.input, input)
|
|
365
|
+
) {
|
|
366
|
+
return {
|
|
367
|
+
...parked.session,
|
|
368
|
+
isResume: true,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// The caller explicitly requested cold restoration, supplied settings
|
|
373
|
+
// that cannot safely reuse the live runtime, or supplied a resume-session
|
|
374
|
+
// state that represents progress beyond the parked continue-turn. Retire
|
|
375
|
+
// the stale live session before rebuilding from the persisted journal
|
|
376
|
+
// with this request's runtime settings.
|
|
377
|
+
await parked.session.doDestroy();
|
|
329
378
|
}
|
|
330
379
|
}
|
|
331
380
|
|
|
@@ -438,6 +487,7 @@ export async function createPiSession(
|
|
|
438
487
|
});
|
|
439
488
|
const modelRuntime = await createPiModelRuntime({
|
|
440
489
|
auth: input.settings.auth,
|
|
490
|
+
credentials: input.settings.credentials,
|
|
441
491
|
authPath: path.join(nativeAgentDir ?? hostAgentDir, 'auth.json'),
|
|
442
492
|
modelsPath: path.join(agentDir, 'models.json'),
|
|
443
493
|
});
|
|
@@ -1521,7 +1571,11 @@ export async function createPiSession(
|
|
|
1521
1571
|
|
|
1522
1572
|
doDetach: async (): Promise<HarnessV1ResumeSessionState> => {
|
|
1523
1573
|
if (activeTurn != null || pendingToolResults.size > 0) {
|
|
1524
|
-
parkedPiSessions.set(input.sessionId,
|
|
1574
|
+
parkedPiSessions.set(input.sessionId, {
|
|
1575
|
+
session: sessionImpl,
|
|
1576
|
+
input,
|
|
1577
|
+
stateType: 'resume-session',
|
|
1578
|
+
});
|
|
1525
1579
|
if (sessionFileName) {
|
|
1526
1580
|
try {
|
|
1527
1581
|
await persistSessionFile();
|
|
@@ -1551,7 +1605,11 @@ export async function createPiSession(
|
|
|
1551
1605
|
activeTurn != null &&
|
|
1552
1606
|
(pendingToolResults.size > 0 || pendingToolApprovals.size > 0)
|
|
1553
1607
|
) {
|
|
1554
|
-
parkedPiSessions.set(input.sessionId,
|
|
1608
|
+
parkedPiSessions.set(input.sessionId, {
|
|
1609
|
+
session: sessionImpl,
|
|
1610
|
+
input,
|
|
1611
|
+
stateType: 'continue-turn',
|
|
1612
|
+
});
|
|
1555
1613
|
if (sessionFileName) {
|
|
1556
1614
|
try {
|
|
1557
1615
|
await persistSessionFile();
|