@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness-pi",
3
- "version": "1.0.36",
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.79.0",
29
+ "@earendil-works/pi-coding-agent": "^0.80.10",
30
30
  "typebox": "^1.1.38",
31
- "@ai-sdk/harness": "1.0.36",
32
- "@ai-sdk/provider-utils": "5.0.11"
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
- function register(
60
- registries: { authStorage: AuthStorage; modelRegistry: ModelRegistry },
61
- provider: string,
62
- apiKey: string,
63
- config: ProviderConfigInput,
64
- ): void {
65
- registries.authStorage.setRuntimeApiKey(provider, apiKey);
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 applyCustomEnv({
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 applyCustomEnv({
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: { authStorage: AuthStorage; modelRegistry: ModelRegistry };
173
+ registries: PiRegistries;
145
174
  clientApp: string;
146
- }): Record<string, string> {
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(registries, 'openai', customEnv.OPENAI_API_KEY, {
193
+ await register({
194
+ registries,
195
+ provider: 'openai',
169
196
  apiKey: customEnv.OPENAI_API_KEY,
170
- baseUrl,
171
- authHeader: true,
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(registries, 'anthropic', customEnv.ANTHROPIC_API_KEY, {
207
+ await register({
208
+ registries,
209
+ provider: 'anthropic',
178
210
  apiKey: customEnv.ANTHROPIC_API_KEY,
179
- baseUrl,
180
- ...(customEnv.ANTHROPIC_AUTH_TOKEN
181
- ? {
182
- headers: {
183
- authorization: `Bearer ${customEnv.ANTHROPIC_AUTH_TOKEN}`,
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(registries, provider, apiKey, {
242
+ await register({
243
+ registries,
244
+ provider,
208
245
  apiKey,
209
- baseUrl,
210
- authHeader: true,
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
  };
@@ -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: ModelRegistry,
16
- env: NodeJS.ProcessEnv = process.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 { resolvePiEnv, type PiAuthOptions } from './pi-auth';
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 (`hostAgentDir`), never in the sandbox/workspace.
324
- const authStorage = AuthStorage.create(path.join(hostAgentDir, 'auth.json'));
325
- const modelRegistry = ModelRegistry.create(
326
- authStorage,
327
- path.join(hostAgentDir, 'models.json'),
328
- );
329
- const settingsManager = SettingsManager.inMemory();
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(modelRegistry, resolverEnv);
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
- authStorage,
583
- modelRegistry,
607
+ modelRuntime,
584
608
  sessionManager,
585
609
  settingsManager,
586
610
  resourceLoader,