@ai-sdk/harness-pi 1.0.36 → 1.0.38
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 +19 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +105 -69
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/src/pi-auth.ts +104 -66
- package/src/pi-harness.ts +8 -0
- package/src/pi-model-resolver.ts +7 -4
- package/src/pi-session.ts +37 -13
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.38",
|
|
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
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
29
|
+
"@earendil-works/pi-coding-agent": "^0.80.10",
|
|
30
30
|
"typebox": "^1.1.38",
|
|
31
|
-
"@ai-sdk/harness": "1.0.
|
|
32
|
-
"@ai-sdk/provider-utils": "5.0.
|
|
31
|
+
"@ai-sdk/harness": "1.0.38",
|
|
32
|
+
"@ai-sdk/provider-utils": "5.0.12"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"zod": "^3.25.76 || ^4.1.8"
|
package/src/pi-auth.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
AuthStorage,
|
|
3
2
|
ModelRegistry,
|
|
3
|
+
ModelRuntime,
|
|
4
4
|
} from '@earendil-works/pi-coding-agent';
|
|
5
5
|
import { getAiGatewayAuthFromEnv } from '@ai-sdk/harness/utils';
|
|
6
6
|
import { VERSION } from './version';
|
|
@@ -56,14 +56,24 @@ function createGatewayProviderConfig({
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
registries
|
|
59
|
+
type PiRegistries = {
|
|
60
|
+
modelRegistry: ModelRegistry;
|
|
61
|
+
modelRuntime: ModelRuntime;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
async function register({
|
|
65
|
+
registries,
|
|
66
|
+
provider,
|
|
67
|
+
apiKey,
|
|
68
|
+
config,
|
|
69
|
+
}: {
|
|
70
|
+
registries: PiRegistries;
|
|
71
|
+
provider: string;
|
|
72
|
+
apiKey: string;
|
|
73
|
+
config: ProviderConfigInput;
|
|
74
|
+
}): Promise<void> {
|
|
66
75
|
registries.modelRegistry.registerProvider(provider, config);
|
|
76
|
+
await registries.modelRuntime.setRuntimeApiKey(provider, apiKey);
|
|
67
77
|
}
|
|
68
78
|
|
|
69
79
|
function hasConfiguredValue(value: unknown): boolean {
|
|
@@ -76,21 +86,13 @@ function hasConfiguredValue(value: unknown): boolean {
|
|
|
76
86
|
export function resolvePiEnv({
|
|
77
87
|
options,
|
|
78
88
|
env,
|
|
79
|
-
registries,
|
|
80
|
-
clientApp = HARNESS_CLIENT_APP,
|
|
81
89
|
}: {
|
|
82
90
|
options: PiAuthOptions | undefined;
|
|
83
91
|
env: NodeJS.ProcessEnv;
|
|
84
|
-
registries: { authStorage: AuthStorage; modelRegistry: ModelRegistry };
|
|
85
|
-
clientApp?: string;
|
|
86
92
|
}): Record<string, string> {
|
|
87
93
|
const customEnvConfigured = hasConfiguredValue(options?.customEnv);
|
|
88
94
|
if (customEnvConfigured) {
|
|
89
|
-
return
|
|
90
|
-
customEnv: options!.customEnv ?? {},
|
|
91
|
-
registries,
|
|
92
|
-
clientApp,
|
|
93
|
-
});
|
|
95
|
+
return resolveCustomEnv({ customEnv: options!.customEnv ?? {} });
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
const gatewayConfigured = hasConfiguredValue(options?.gateway);
|
|
@@ -99,16 +101,6 @@ export function resolvePiEnv({
|
|
|
99
101
|
const apiKey = options!.gateway?.apiKey ?? gatewayAuthFromEnv.apiKey;
|
|
100
102
|
const baseUrl = options!.gateway?.baseUrl ?? gatewayAuthFromEnv.baseUrl;
|
|
101
103
|
if (apiKey) {
|
|
102
|
-
register(
|
|
103
|
-
registries,
|
|
104
|
-
'vercel-ai-gateway',
|
|
105
|
-
apiKey,
|
|
106
|
-
createGatewayProviderConfig({
|
|
107
|
-
apiKey,
|
|
108
|
-
baseUrl,
|
|
109
|
-
clientApp,
|
|
110
|
-
}),
|
|
111
|
-
);
|
|
112
104
|
return { AI_GATEWAY_API_KEY: apiKey, AI_GATEWAY_BASE_URL: baseUrl };
|
|
113
105
|
}
|
|
114
106
|
return {};
|
|
@@ -116,16 +108,6 @@ export function resolvePiEnv({
|
|
|
116
108
|
|
|
117
109
|
// Ambient gateway fallback.
|
|
118
110
|
if (gatewayAuthFromEnv.apiKey) {
|
|
119
|
-
register(
|
|
120
|
-
registries,
|
|
121
|
-
'vercel-ai-gateway',
|
|
122
|
-
gatewayAuthFromEnv.apiKey,
|
|
123
|
-
createGatewayProviderConfig({
|
|
124
|
-
apiKey: gatewayAuthFromEnv.apiKey,
|
|
125
|
-
baseUrl: gatewayAuthFromEnv.baseUrl,
|
|
126
|
-
clientApp,
|
|
127
|
-
}),
|
|
128
|
-
);
|
|
129
111
|
return {
|
|
130
112
|
AI_GATEWAY_API_KEY: gatewayAuthFromEnv.apiKey,
|
|
131
113
|
AI_GATEWAY_BASE_URL: gatewayAuthFromEnv.baseUrl,
|
|
@@ -135,55 +117,108 @@ export function resolvePiEnv({
|
|
|
135
117
|
return {};
|
|
136
118
|
}
|
|
137
119
|
|
|
138
|
-
function
|
|
120
|
+
export async function registerPiProviders({
|
|
121
|
+
options,
|
|
122
|
+
resolvedEnv,
|
|
123
|
+
registries,
|
|
124
|
+
clientApp = HARNESS_CLIENT_APP,
|
|
125
|
+
}: {
|
|
126
|
+
options: PiAuthOptions | undefined;
|
|
127
|
+
resolvedEnv: Record<string, string>;
|
|
128
|
+
registries: PiRegistries;
|
|
129
|
+
clientApp?: string;
|
|
130
|
+
}): Promise<void> {
|
|
131
|
+
if (hasConfiguredValue(options?.customEnv)) {
|
|
132
|
+
await registerCustomProviders({
|
|
133
|
+
customEnv: options!.customEnv ?? {},
|
|
134
|
+
registries,
|
|
135
|
+
clientApp,
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const apiKey = resolvedEnv.AI_GATEWAY_API_KEY;
|
|
141
|
+
const baseUrl = resolvedEnv.AI_GATEWAY_BASE_URL;
|
|
142
|
+
if (!apiKey || !baseUrl) return;
|
|
143
|
+
|
|
144
|
+
await register({
|
|
145
|
+
registries,
|
|
146
|
+
provider: 'vercel-ai-gateway',
|
|
147
|
+
apiKey,
|
|
148
|
+
config: createGatewayProviderConfig({ apiKey, baseUrl, clientApp }),
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function resolveCustomEnv({
|
|
153
|
+
customEnv,
|
|
154
|
+
}: {
|
|
155
|
+
customEnv: Record<string, string>;
|
|
156
|
+
}): Record<string, string> {
|
|
157
|
+
const apiKey = customEnv.AI_GATEWAY_API_KEY;
|
|
158
|
+
if (!apiKey) return {};
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
AI_GATEWAY_API_KEY: apiKey,
|
|
162
|
+
AI_GATEWAY_BASE_URL:
|
|
163
|
+
customEnv.AI_GATEWAY_BASE_URL ?? DEFAULT_GATEWAY_BASE_URL,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function registerCustomProviders({
|
|
139
168
|
customEnv,
|
|
140
169
|
registries,
|
|
141
170
|
clientApp,
|
|
142
171
|
}: {
|
|
143
172
|
customEnv: Record<string, string>;
|
|
144
|
-
registries:
|
|
173
|
+
registries: PiRegistries;
|
|
145
174
|
clientApp: string;
|
|
146
|
-
}):
|
|
147
|
-
const out: Record<string, string> = {};
|
|
148
|
-
|
|
175
|
+
}): Promise<void> {
|
|
149
176
|
const gatewayKey = customEnv.AI_GATEWAY_API_KEY;
|
|
150
177
|
if (gatewayKey) {
|
|
151
178
|
const baseUrl = customEnv.AI_GATEWAY_BASE_URL ?? DEFAULT_GATEWAY_BASE_URL;
|
|
152
|
-
register(
|
|
179
|
+
await register({
|
|
153
180
|
registries,
|
|
154
|
-
'vercel-ai-gateway',
|
|
155
|
-
gatewayKey,
|
|
156
|
-
createGatewayProviderConfig({
|
|
181
|
+
provider: 'vercel-ai-gateway',
|
|
182
|
+
apiKey: gatewayKey,
|
|
183
|
+
config: createGatewayProviderConfig({
|
|
157
184
|
apiKey: gatewayKey,
|
|
158
185
|
baseUrl,
|
|
159
186
|
clientApp,
|
|
160
187
|
}),
|
|
161
|
-
);
|
|
162
|
-
out.AI_GATEWAY_API_KEY = gatewayKey;
|
|
163
|
-
out.AI_GATEWAY_BASE_URL = baseUrl;
|
|
188
|
+
});
|
|
164
189
|
}
|
|
165
190
|
|
|
166
191
|
if (customEnv.OPENAI_API_KEY) {
|
|
167
192
|
const baseUrl = customEnv.OPENAI_BASE_URL ?? DEFAULT_OPENAI_BASE_URL;
|
|
168
|
-
register(
|
|
193
|
+
await register({
|
|
194
|
+
registries,
|
|
195
|
+
provider: 'openai',
|
|
169
196
|
apiKey: customEnv.OPENAI_API_KEY,
|
|
170
|
-
|
|
171
|
-
|
|
197
|
+
config: {
|
|
198
|
+
apiKey: customEnv.OPENAI_API_KEY,
|
|
199
|
+
baseUrl,
|
|
200
|
+
authHeader: true,
|
|
201
|
+
},
|
|
172
202
|
});
|
|
173
203
|
}
|
|
174
204
|
|
|
175
205
|
if (customEnv.ANTHROPIC_API_KEY) {
|
|
176
206
|
const baseUrl = customEnv.ANTHROPIC_BASE_URL ?? DEFAULT_ANTHROPIC_BASE_URL;
|
|
177
|
-
register(
|
|
207
|
+
await register({
|
|
208
|
+
registries,
|
|
209
|
+
provider: 'anthropic',
|
|
178
210
|
apiKey: customEnv.ANTHROPIC_API_KEY,
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
211
|
+
config: {
|
|
212
|
+
apiKey: customEnv.ANTHROPIC_API_KEY,
|
|
213
|
+
baseUrl,
|
|
214
|
+
...(customEnv.ANTHROPIC_AUTH_TOKEN
|
|
215
|
+
? {
|
|
216
|
+
headers: {
|
|
217
|
+
authorization: `Bearer ${customEnv.ANTHROPIC_AUTH_TOKEN}`,
|
|
218
|
+
},
|
|
219
|
+
}
|
|
220
|
+
: {}),
|
|
221
|
+
},
|
|
187
222
|
});
|
|
188
223
|
}
|
|
189
224
|
|
|
@@ -204,12 +239,15 @@ function applyCustomEnv({
|
|
|
204
239
|
if (!baseUrl) {
|
|
205
240
|
continue;
|
|
206
241
|
}
|
|
207
|
-
register(
|
|
242
|
+
await register({
|
|
243
|
+
registries,
|
|
244
|
+
provider,
|
|
208
245
|
apiKey,
|
|
209
|
-
|
|
210
|
-
|
|
246
|
+
config: {
|
|
247
|
+
apiKey,
|
|
248
|
+
baseUrl,
|
|
249
|
+
authHeader: true,
|
|
250
|
+
},
|
|
211
251
|
});
|
|
212
252
|
}
|
|
213
|
-
|
|
214
|
-
return out;
|
|
215
253
|
}
|
package/src/pi-harness.ts
CHANGED
|
@@ -33,6 +33,13 @@ export type PiHarnessSettings = {
|
|
|
33
33
|
* `thinkingLevel` option on `createAgentSession`.
|
|
34
34
|
*/
|
|
35
35
|
readonly thinkingLevel?: PiThinkingLevel;
|
|
36
|
+
/**
|
|
37
|
+
* Directory holding Pi's global agent config (auth.json, models.json,
|
|
38
|
+
* settings.json). When omitted, a per-session temp dir is used. Pass the
|
|
39
|
+
* user's agent dir (e.g. `~/.pi/agent/`) to reuse their CLI auth and
|
|
40
|
+
* model settings.
|
|
41
|
+
*/
|
|
42
|
+
readonly agentDir?: string;
|
|
36
43
|
};
|
|
37
44
|
|
|
38
45
|
const PI_BUILTIN_TOOLS = {
|
|
@@ -148,6 +155,7 @@ export function createPi(
|
|
|
148
155
|
...(startOpts.abortSignal
|
|
149
156
|
? { abortSignal: startOpts.abortSignal }
|
|
150
157
|
: {}),
|
|
158
|
+
...(settings.agentDir ? { agentDir: settings.agentDir } : {}),
|
|
151
159
|
});
|
|
152
160
|
},
|
|
153
161
|
};
|
package/src/pi-model-resolver.ts
CHANGED
|
@@ -11,10 +11,13 @@ type PiModel = ReturnType<ModelRegistry['getAll']>[number];
|
|
|
11
11
|
*/
|
|
12
12
|
export const DEFAULT_PI_GATEWAY_MODEL_ID = 'anthropic/claude-sonnet-4.6';
|
|
13
13
|
|
|
14
|
-
export function createPiModelResolver(
|
|
15
|
-
modelRegistry
|
|
16
|
-
env
|
|
17
|
-
|
|
14
|
+
export function createPiModelResolver({
|
|
15
|
+
modelRegistry,
|
|
16
|
+
env = process.env,
|
|
17
|
+
}: {
|
|
18
|
+
modelRegistry: ModelRegistry;
|
|
19
|
+
env?: NodeJS.ProcessEnv;
|
|
20
|
+
}) {
|
|
18
21
|
let cachedModels: PiModel[] | undefined;
|
|
19
22
|
|
|
20
23
|
const loadModels = (): PiModel[] => {
|
package/src/pi-session.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AuthStorage,
|
|
3
2
|
createAgentSession,
|
|
4
3
|
DefaultResourceLoader,
|
|
5
4
|
defineTool,
|
|
6
5
|
ModelRegistry,
|
|
6
|
+
ModelRuntime,
|
|
7
7
|
SessionManager,
|
|
8
8
|
SettingsManager,
|
|
9
9
|
type AgentSession,
|
|
@@ -30,7 +30,11 @@ import {
|
|
|
30
30
|
type HarnessV1ToolSpec,
|
|
31
31
|
} from '@ai-sdk/harness';
|
|
32
32
|
import { resolveSandboxHomeDir } from '@ai-sdk/harness/utils';
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
registerPiProviders,
|
|
35
|
+
resolvePiEnv,
|
|
36
|
+
type PiAuthOptions,
|
|
37
|
+
} from './pi-auth';
|
|
34
38
|
import { getPiTerminalError, parseNativeEvent } from './pi-events';
|
|
35
39
|
import { createPiModelResolver } from './pi-model-resolver';
|
|
36
40
|
import { createPiPathMapper } from './pi-paths';
|
|
@@ -202,6 +206,13 @@ export interface CreatePiSessionInput {
|
|
|
202
206
|
readonly builtinToolFiltering?: HarnessV1BuiltinToolFiltering;
|
|
203
207
|
readonly resumeSessionFileName?: string;
|
|
204
208
|
readonly abortSignal?: AbortSignal;
|
|
209
|
+
/**
|
|
210
|
+
* Directory holding Pi's global agent config (auth.json, models.json,
|
|
211
|
+
* settings.json). When omitted, a per-session temp dir is used (the
|
|
212
|
+
* harness cannot reuse existing CLI logins). Pass the user's agent dir
|
|
213
|
+
* (e.g. `~/.pi/agent/`) to reuse their CLI auth and model settings.
|
|
214
|
+
*/
|
|
215
|
+
readonly agentDir?: string;
|
|
205
216
|
}
|
|
206
217
|
|
|
207
218
|
interface PendingToolResult {
|
|
@@ -320,25 +331,39 @@ export async function createPiSession(
|
|
|
320
331
|
});
|
|
321
332
|
|
|
322
333
|
// Pi auth + model registry are global to this Pi session. These live on the
|
|
323
|
-
// real host filesystem
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
334
|
+
// real host filesystem, never in the sandbox/workspace.
|
|
335
|
+
// When `agentDir` is provided, use it instead so the harness can reuse
|
|
336
|
+
// existing CLI logins and model/settings config.
|
|
337
|
+
const agentDir = input.agentDir ?? hostAgentDir;
|
|
338
|
+
const modelRuntime = await ModelRuntime.create({
|
|
339
|
+
authPath: path.join(agentDir, 'auth.json'),
|
|
340
|
+
modelsPath: path.join(agentDir, 'models.json'),
|
|
341
|
+
allowModelNetwork: false,
|
|
342
|
+
});
|
|
343
|
+
const modelRegistry = new ModelRegistry(modelRuntime);
|
|
344
|
+
const settingsManager =
|
|
345
|
+
input.agentDir != null
|
|
346
|
+
? SettingsManager.create(hostWorkDir, agentDir)
|
|
347
|
+
: SettingsManager.inMemory();
|
|
330
348
|
|
|
331
349
|
// Run-scoped env (for the model resolver's gateway fallback heuristic).
|
|
332
350
|
const resolverEnv = resolvePiEnv({
|
|
333
351
|
options: input.settings.auth,
|
|
334
352
|
env: process.env,
|
|
353
|
+
});
|
|
354
|
+
await registerPiProviders({
|
|
355
|
+
options: input.settings.auth,
|
|
356
|
+
resolvedEnv: resolverEnv,
|
|
335
357
|
registries: {
|
|
336
|
-
authStorage,
|
|
337
358
|
modelRegistry,
|
|
359
|
+
modelRuntime,
|
|
338
360
|
},
|
|
339
361
|
clientApp: input.clientApp,
|
|
340
362
|
});
|
|
341
|
-
const resolveModel = createPiModelResolver(
|
|
363
|
+
const resolveModel = createPiModelResolver({
|
|
364
|
+
modelRegistry,
|
|
365
|
+
env: resolverEnv,
|
|
366
|
+
});
|
|
342
367
|
// Resolve once: deterministic given the configured model. This is the Pi
|
|
343
368
|
// `Model` object handed to `createAgentSession`.
|
|
344
369
|
const resolvedModel = resolveModel(input.settings.model);
|
|
@@ -579,8 +604,7 @@ export async function createPiSession(
|
|
|
579
604
|
const { session } = await createAgentSession({
|
|
580
605
|
cwd: sessionWorkDir,
|
|
581
606
|
agentDir: hostAgentDir,
|
|
582
|
-
|
|
583
|
-
modelRegistry,
|
|
607
|
+
modelRuntime,
|
|
584
608
|
sessionManager,
|
|
585
609
|
settingsManager,
|
|
586
610
|
resourceLoader,
|