@ai-sdk/harness-pi 1.0.92 → 1.0.94
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 +14 -0
- package/dist/index.d.ts +4 -25
- package/dist/index.js +141 -95
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/index.ts +1 -1
- package/src/pi-auth.ts +194 -146
- package/src/pi-harness.ts +2 -2
- package/src/pi-session.ts +10 -5
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.94",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@ai-sdk/harness": "1.0.
|
|
29
|
+
"@ai-sdk/harness": "1.0.92",
|
|
30
30
|
"@ai-sdk/provider-utils": "5.0.32",
|
|
31
31
|
"@earendil-works/pi-ai": "0.74.2",
|
|
32
32
|
"@earendil-works/pi-coding-agent": "^0.80.10",
|
|
@@ -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.92",
|
|
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
|
|
12
|
+
export type { PiAuthenticationMode } from './pi-auth';
|
package/src/pi-auth.ts
CHANGED
|
@@ -1,51 +1,175 @@
|
|
|
1
|
-
import
|
|
2
|
-
ModelRegistry,
|
|
1
|
+
import {
|
|
3
2
|
ModelRuntime,
|
|
3
|
+
type CreateModelRuntimeOptions,
|
|
4
|
+
type ModelRegistry,
|
|
4
5
|
} from '@earendil-works/pi-coding-agent';
|
|
5
|
-
import {
|
|
6
|
+
import type { HarnessV1Authentication } from '@ai-sdk/harness';
|
|
7
|
+
import {
|
|
8
|
+
getAiGatewayAuthFromEnv,
|
|
9
|
+
isHarnessAuthenticationEnvironment,
|
|
10
|
+
} from '@ai-sdk/harness/utils';
|
|
11
|
+
import { access } from 'node:fs/promises';
|
|
6
12
|
import { VERSION } from './version';
|
|
7
13
|
|
|
8
14
|
type ProviderConfigInput = Parameters<ModelRegistry['registerProvider']>[1];
|
|
15
|
+
type PiCredentialStore = NonNullable<CreateModelRuntimeOptions['credentials']>;
|
|
16
|
+
type PiCredential = Exclude<
|
|
17
|
+
Awaited<ReturnType<PiCredentialStore['read']>>,
|
|
18
|
+
undefined
|
|
19
|
+
>;
|
|
20
|
+
type PiModelRuntimeInternals = {
|
|
21
|
+
models: {
|
|
22
|
+
authContext: {
|
|
23
|
+
env(name: string): Promise<string | undefined>;
|
|
24
|
+
fileExists(path: string): Promise<boolean>;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
type PiMutableProvider = {
|
|
29
|
+
auth: ReturnType<ModelRuntime['getProviders']>[number]['auth'];
|
|
30
|
+
};
|
|
9
31
|
|
|
10
32
|
/**
|
|
11
33
|
* Pi auth options. Choose an explicit mode or rely on 'auto' (precedence:
|
|
12
34
|
* explicit gateway, then OpenAI / Anthropic / custom environment variables).
|
|
13
35
|
*/
|
|
14
|
-
export type PiAuthenticationMode =
|
|
15
|
-
| '
|
|
16
|
-
|
|
17
|
-
| 'anthropic'
|
|
18
|
-
| 'custom'
|
|
19
|
-
| 'ai-gateway';
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @deprecated Passing an object to auth options is deprecated. Use a `PiAuthenticationMode` string value ("auto" | "openai" | "anthropic" | "custom" | "ai-gateway") instead, and pass credentials via environment variables.
|
|
23
|
-
*/
|
|
24
|
-
export type LegacyPiAuthOptions = {
|
|
25
|
-
readonly gateway?: {
|
|
26
|
-
readonly apiKey?: string;
|
|
27
|
-
readonly baseUrl?: string;
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* Resolved environment-variable pairs of the form `<PREFIX>_API_KEY` and
|
|
31
|
-
* (optionally) `<PREFIX>_BASE_URL`. Special-cased prefixes:
|
|
32
|
-
* - `AI_GATEWAY` → registers `vercel-ai-gateway`
|
|
33
|
-
* - `OPENAI` → registers `openai`
|
|
34
|
-
* - `ANTHROPIC` → registers `anthropic` (`ANTHROPIC_AUTH_TOKEN` adds a
|
|
35
|
-
* bearer auth header)
|
|
36
|
-
* Any other `<PREFIX>_API_KEY` with a matching `<PREFIX>_BASE_URL` is
|
|
37
|
-
* registered as the lowercased, dash-separated prefix.
|
|
38
|
-
*/
|
|
39
|
-
readonly customEnv?: Record<string, string>;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
export type PiAuthOptions = PiAuthenticationMode | LegacyPiAuthOptions;
|
|
36
|
+
export type PiAuthenticationMode = HarnessV1Authentication<
|
|
37
|
+
'openai' | 'anthropic' | 'custom'
|
|
38
|
+
>;
|
|
43
39
|
|
|
44
40
|
const DEFAULT_GATEWAY_BASE_URL = 'https://ai-gateway.vercel.sh';
|
|
45
41
|
const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';
|
|
46
42
|
const DEFAULT_ANTHROPIC_BASE_URL = 'https://api.anthropic.com';
|
|
47
43
|
const HARNESS_CLIENT_APP = `ai-sdk/harness-pi/${VERSION}`;
|
|
48
44
|
|
|
45
|
+
function createIsolatedPiCredentialStore(): {
|
|
46
|
+
credentials: PiCredentialStore;
|
|
47
|
+
finishInitialization(): void;
|
|
48
|
+
} {
|
|
49
|
+
let initializing = true;
|
|
50
|
+
const bootstrapEnvironment = new Proxy<Record<string, string>>(
|
|
51
|
+
{},
|
|
52
|
+
{
|
|
53
|
+
get: (_target, property) =>
|
|
54
|
+
typeof property === 'string'
|
|
55
|
+
? 'harness-pi-authentication-bootstrap'
|
|
56
|
+
: undefined,
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
const bootstrapCredential = {
|
|
60
|
+
type: 'api_key',
|
|
61
|
+
key: 'harness-pi-authentication-bootstrap',
|
|
62
|
+
env: bootstrapEnvironment,
|
|
63
|
+
} satisfies PiCredential;
|
|
64
|
+
const credentials: PiCredentialStore = {
|
|
65
|
+
async read() {
|
|
66
|
+
return initializing ? bootstrapCredential : undefined;
|
|
67
|
+
},
|
|
68
|
+
async list() {
|
|
69
|
+
return [];
|
|
70
|
+
},
|
|
71
|
+
async modify(..._input: Parameters<PiCredentialStore['modify']>) {
|
|
72
|
+
return undefined;
|
|
73
|
+
},
|
|
74
|
+
async delete() {},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
credentials,
|
|
79
|
+
finishInitialization() {
|
|
80
|
+
initializing = false;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function scopePiProviderEnvironment({
|
|
86
|
+
modelRuntime,
|
|
87
|
+
authenticationEnvironment,
|
|
88
|
+
}: {
|
|
89
|
+
modelRuntime: ModelRuntime;
|
|
90
|
+
authenticationEnvironment: Record<string, string>;
|
|
91
|
+
}): void {
|
|
92
|
+
for (const provider of modelRuntime.getProviders()) {
|
|
93
|
+
const apiKeyAuthentication = provider.auth.apiKey;
|
|
94
|
+
if (!apiKeyAuthentication) continue;
|
|
95
|
+
|
|
96
|
+
(provider as unknown as PiMutableProvider).auth = {
|
|
97
|
+
...provider.auth,
|
|
98
|
+
apiKey: {
|
|
99
|
+
...apiKeyAuthentication,
|
|
100
|
+
resolve: async input => {
|
|
101
|
+
const result = await apiKeyAuthentication.resolve(input);
|
|
102
|
+
return result
|
|
103
|
+
? {
|
|
104
|
+
...result,
|
|
105
|
+
env: {
|
|
106
|
+
...authenticationEnvironment,
|
|
107
|
+
...result.env,
|
|
108
|
+
},
|
|
109
|
+
}
|
|
110
|
+
: undefined;
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function createPiModelRuntime({
|
|
118
|
+
auth,
|
|
119
|
+
authPath,
|
|
120
|
+
modelsPath,
|
|
121
|
+
}: {
|
|
122
|
+
auth: PiAuthenticationMode | undefined;
|
|
123
|
+
authPath: string;
|
|
124
|
+
modelsPath: string;
|
|
125
|
+
}): Promise<ModelRuntime> {
|
|
126
|
+
if (!isHarnessAuthenticationEnvironment(auth)) {
|
|
127
|
+
return ModelRuntime.create({
|
|
128
|
+
authPath,
|
|
129
|
+
modelsPath,
|
|
130
|
+
allowModelNetwork: false,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const isolatedCredentials = createIsolatedPiCredentialStore();
|
|
135
|
+
const modelRuntime = await ModelRuntime.create({
|
|
136
|
+
credentials: isolatedCredentials.credentials,
|
|
137
|
+
modelsPath: null,
|
|
138
|
+
allowModelNetwork: false,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/*
|
|
142
|
+
* ModelRuntime creates its internal model collection with a process-backed
|
|
143
|
+
* authentication context and does not expose an authentication-context
|
|
144
|
+
* option. The bootstrap credential prevents construction-time provider
|
|
145
|
+
* checks from consulting that context. Once constructed, authentication is
|
|
146
|
+
* scoped to the supplied record and availability is recomputed with an
|
|
147
|
+
* empty in-memory credential store.
|
|
148
|
+
*/
|
|
149
|
+
(modelRuntime as unknown as PiModelRuntimeInternals).models.authContext = {
|
|
150
|
+
async env(name) {
|
|
151
|
+
return auth[name];
|
|
152
|
+
},
|
|
153
|
+
async fileExists(filePath) {
|
|
154
|
+
if (filePath !== auth.GOOGLE_APPLICATION_CREDENTIALS) return false;
|
|
155
|
+
try {
|
|
156
|
+
await access(filePath);
|
|
157
|
+
return true;
|
|
158
|
+
} catch {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
scopePiProviderEnvironment({
|
|
164
|
+
modelRuntime,
|
|
165
|
+
authenticationEnvironment: auth,
|
|
166
|
+
});
|
|
167
|
+
isolatedCredentials.finishInitialization();
|
|
168
|
+
await modelRuntime.refresh({ allowNetwork: false });
|
|
169
|
+
|
|
170
|
+
return modelRuntime;
|
|
171
|
+
}
|
|
172
|
+
|
|
49
173
|
function createGatewayProviderConfig({
|
|
50
174
|
apiKey,
|
|
51
175
|
baseUrl,
|
|
@@ -86,68 +210,54 @@ async function register({
|
|
|
86
210
|
await registries.modelRuntime.setRuntimeApiKey(provider, apiKey);
|
|
87
211
|
}
|
|
88
212
|
|
|
89
|
-
function hasConfiguredValue(value: unknown): boolean {
|
|
90
|
-
if (value == null) return false;
|
|
91
|
-
if (typeof value === 'string') return value.length > 0;
|
|
92
|
-
if (typeof value !== 'object') return true;
|
|
93
|
-
return Object.values(value).some(hasConfiguredValue);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
213
|
export function resolvePiEnv({
|
|
97
214
|
options,
|
|
98
215
|
env,
|
|
99
216
|
}: {
|
|
100
|
-
options:
|
|
217
|
+
options: PiAuthenticationMode | undefined;
|
|
101
218
|
env: NodeJS.ProcessEnv;
|
|
102
219
|
}): Record<string, string> {
|
|
103
|
-
const
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const gatewayConfigured = hasConfiguredValue(normalizedOptions?.gateway);
|
|
110
|
-
const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({ env });
|
|
111
|
-
if (gatewayConfigured) {
|
|
112
|
-
const apiKey =
|
|
113
|
-
normalizedOptions!.gateway?.apiKey ?? gatewayAuthFromEnv.apiKey;
|
|
114
|
-
const baseUrl =
|
|
115
|
-
normalizedOptions!.gateway?.baseUrl ?? gatewayAuthFromEnv.baseUrl;
|
|
116
|
-
if (apiKey) {
|
|
117
|
-
return { AI_GATEWAY_API_KEY: apiKey, AI_GATEWAY_BASE_URL: baseUrl };
|
|
118
|
-
}
|
|
119
|
-
return {};
|
|
120
|
-
}
|
|
220
|
+
const suppliedEnvironment = isHarnessAuthenticationEnvironment(options);
|
|
221
|
+
const authenticationEnvironment = suppliedEnvironment ? options : env;
|
|
222
|
+
const gatewayAuthFromEnv = getAiGatewayAuthFromEnv({
|
|
223
|
+
env: authenticationEnvironment,
|
|
224
|
+
});
|
|
121
225
|
|
|
122
226
|
// Handle explicit string modes with process env
|
|
123
227
|
if (typeof options === 'string') {
|
|
124
228
|
switch (options) {
|
|
125
229
|
case 'openai':
|
|
126
|
-
if (
|
|
230
|
+
if (authenticationEnvironment.OPENAI_API_KEY) {
|
|
127
231
|
return {
|
|
128
|
-
OPENAI_API_KEY:
|
|
129
|
-
...(
|
|
130
|
-
? { OPENAI_BASE_URL:
|
|
232
|
+
OPENAI_API_KEY: authenticationEnvironment.OPENAI_API_KEY,
|
|
233
|
+
...(authenticationEnvironment.OPENAI_BASE_URL
|
|
234
|
+
? { OPENAI_BASE_URL: authenticationEnvironment.OPENAI_BASE_URL }
|
|
131
235
|
: {}),
|
|
132
236
|
};
|
|
133
237
|
}
|
|
134
238
|
return {};
|
|
135
239
|
case 'anthropic':
|
|
136
|
-
if (
|
|
240
|
+
if (authenticationEnvironment.ANTHROPIC_API_KEY) {
|
|
137
241
|
return {
|
|
138
|
-
ANTHROPIC_API_KEY:
|
|
139
|
-
...(
|
|
140
|
-
? {
|
|
242
|
+
ANTHROPIC_API_KEY: authenticationEnvironment.ANTHROPIC_API_KEY,
|
|
243
|
+
...(authenticationEnvironment.ANTHROPIC_BASE_URL
|
|
244
|
+
? {
|
|
245
|
+
ANTHROPIC_BASE_URL:
|
|
246
|
+
authenticationEnvironment.ANTHROPIC_BASE_URL,
|
|
247
|
+
}
|
|
141
248
|
: {}),
|
|
142
|
-
...(
|
|
143
|
-
? {
|
|
249
|
+
...(authenticationEnvironment.ANTHROPIC_AUTH_TOKEN
|
|
250
|
+
? {
|
|
251
|
+
ANTHROPIC_AUTH_TOKEN:
|
|
252
|
+
authenticationEnvironment.ANTHROPIC_AUTH_TOKEN,
|
|
253
|
+
}
|
|
144
254
|
: {}),
|
|
145
255
|
};
|
|
146
256
|
}
|
|
147
257
|
return {};
|
|
148
258
|
case 'custom': {
|
|
149
259
|
const result: Record<string, string> = {};
|
|
150
|
-
for (const [key, value] of Object.entries(
|
|
260
|
+
for (const [key, value] of Object.entries(authenticationEnvironment)) {
|
|
151
261
|
if (
|
|
152
262
|
value &&
|
|
153
263
|
(key.endsWith('_API_KEY') ||
|
|
@@ -183,7 +293,7 @@ export function resolvePiEnv({
|
|
|
183
293
|
|
|
184
294
|
// 'auto' fallback: pick up any other provider credentials from the env.
|
|
185
295
|
const ambient: Record<string, string> = {};
|
|
186
|
-
for (const [key, value] of Object.entries(
|
|
296
|
+
for (const [key, value] of Object.entries(authenticationEnvironment)) {
|
|
187
297
|
if (
|
|
188
298
|
value &&
|
|
189
299
|
(key.endsWith('_API_KEY') ||
|
|
@@ -202,32 +312,20 @@ export async function registerPiProviders({
|
|
|
202
312
|
registries,
|
|
203
313
|
clientApp = HARNESS_CLIENT_APP,
|
|
204
314
|
}: {
|
|
205
|
-
options:
|
|
315
|
+
options: PiAuthenticationMode | undefined;
|
|
206
316
|
resolvedEnv: Record<string, string>;
|
|
207
317
|
registries: PiRegistries;
|
|
208
318
|
clientApp?: string;
|
|
209
319
|
}): Promise<void> {
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
customEnv: normalizedOptions!.customEnv ?? {},
|
|
214
|
-
registries,
|
|
215
|
-
clientApp,
|
|
216
|
-
});
|
|
217
|
-
return;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// Legacy customEnv was handled above. Everything else reduces to a mode:
|
|
221
|
-
// string modes pass through, `undefined` is 'auto', and legacy gateway
|
|
222
|
-
// objects fall through to the trailing gateway-registration block.
|
|
223
|
-
const mode =
|
|
224
|
-
typeof options === 'string' ? options : options == null ? 'auto' : 'legacy';
|
|
320
|
+
const suppliedEnvironment = isHarnessAuthenticationEnvironment(options);
|
|
321
|
+
const authenticationEnvironment = suppliedEnvironment ? options : process.env;
|
|
322
|
+
const mode = typeof options === 'string' ? options : 'auto';
|
|
225
323
|
|
|
226
324
|
switch (mode) {
|
|
227
325
|
case 'openai': {
|
|
228
326
|
const env = pickOpenAIEnv(resolvedEnv);
|
|
229
327
|
await registerCustomProviders({
|
|
230
|
-
customEnv: { ...pickOpenAIEnv(
|
|
328
|
+
customEnv: { ...pickOpenAIEnv(authenticationEnvironment), ...env },
|
|
231
329
|
registries,
|
|
232
330
|
clientApp,
|
|
233
331
|
});
|
|
@@ -236,7 +334,7 @@ export async function registerPiProviders({
|
|
|
236
334
|
case 'anthropic': {
|
|
237
335
|
const env = pickAnthropicEnv(resolvedEnv);
|
|
238
336
|
await registerCustomProviders({
|
|
239
|
-
customEnv: { ...pickAnthropicEnv(
|
|
337
|
+
customEnv: { ...pickAnthropicEnv(authenticationEnvironment), ...env },
|
|
240
338
|
registries,
|
|
241
339
|
clientApp,
|
|
242
340
|
});
|
|
@@ -246,14 +344,16 @@ export async function registerPiProviders({
|
|
|
246
344
|
// 'custom' registers every provider with credentials in the env.
|
|
247
345
|
const env = pickProviderEnv(resolvedEnv);
|
|
248
346
|
await registerCustomProviders({
|
|
249
|
-
customEnv: { ...pickProviderEnv(
|
|
347
|
+
customEnv: { ...pickProviderEnv(authenticationEnvironment), ...env },
|
|
250
348
|
registries,
|
|
251
349
|
clientApp,
|
|
252
350
|
});
|
|
253
351
|
return;
|
|
254
352
|
}
|
|
255
353
|
case 'ai-gateway': {
|
|
256
|
-
const gatewayAuth = getAiGatewayAuthFromEnv({
|
|
354
|
+
const gatewayAuth = getAiGatewayAuthFromEnv({
|
|
355
|
+
env: authenticationEnvironment,
|
|
356
|
+
});
|
|
257
357
|
const gatewayApiKey =
|
|
258
358
|
resolvedEnv.AI_GATEWAY_API_KEY ?? gatewayAuth.apiKey;
|
|
259
359
|
const gatewayBaseUrl =
|
|
@@ -271,13 +371,13 @@ export async function registerPiProviders({
|
|
|
271
371
|
});
|
|
272
372
|
return;
|
|
273
373
|
}
|
|
274
|
-
case 'legacy':
|
|
275
|
-
break; // handled below
|
|
276
374
|
case 'auto':
|
|
277
375
|
default: {
|
|
278
376
|
// 'auto' (the default): prefer the AI Gateway; only when no gateway
|
|
279
377
|
// credentials exist, fall back to other providers found in the env.
|
|
280
|
-
const gatewayAuth = getAiGatewayAuthFromEnv({
|
|
378
|
+
const gatewayAuth = getAiGatewayAuthFromEnv({
|
|
379
|
+
env: authenticationEnvironment,
|
|
380
|
+
});
|
|
281
381
|
const gatewayApiKey =
|
|
282
382
|
resolvedEnv.AI_GATEWAY_API_KEY ?? gatewayAuth.apiKey;
|
|
283
383
|
const gatewayBaseUrl =
|
|
@@ -297,25 +397,13 @@ export async function registerPiProviders({
|
|
|
297
397
|
}
|
|
298
398
|
const env = pickProviderEnv(resolvedEnv);
|
|
299
399
|
await registerCustomProviders({
|
|
300
|
-
customEnv: { ...pickProviderEnv(
|
|
400
|
+
customEnv: { ...pickProviderEnv(authenticationEnvironment), ...env },
|
|
301
401
|
registries,
|
|
302
402
|
clientApp,
|
|
303
403
|
});
|
|
304
404
|
return;
|
|
305
405
|
}
|
|
306
406
|
}
|
|
307
|
-
|
|
308
|
-
// Legacy explicit gateway object options.
|
|
309
|
-
const apiKey = resolvedEnv.AI_GATEWAY_API_KEY;
|
|
310
|
-
const baseUrl = resolvedEnv.AI_GATEWAY_BASE_URL;
|
|
311
|
-
if (!apiKey || !baseUrl) return;
|
|
312
|
-
|
|
313
|
-
await register({
|
|
314
|
-
registries,
|
|
315
|
-
provider: 'vercel-ai-gateway',
|
|
316
|
-
apiKey,
|
|
317
|
-
config: createGatewayProviderConfig({ apiKey, baseUrl, clientApp }),
|
|
318
|
-
});
|
|
319
407
|
}
|
|
320
408
|
|
|
321
409
|
function pickOpenAIEnv(
|
|
@@ -363,46 +451,6 @@ function pickProviderEnv(
|
|
|
363
451
|
return result;
|
|
364
452
|
}
|
|
365
453
|
|
|
366
|
-
function normalizePiAuthToLegacyAuth(
|
|
367
|
-
options: PiAuthOptions | undefined,
|
|
368
|
-
): LegacyPiAuthOptions | undefined {
|
|
369
|
-
if (options == null || options === 'auto') {
|
|
370
|
-
return undefined;
|
|
371
|
-
}
|
|
372
|
-
if (typeof options === 'string') {
|
|
373
|
-
switch (options) {
|
|
374
|
-
case 'ai-gateway':
|
|
375
|
-
return { gateway: {} };
|
|
376
|
-
case 'custom':
|
|
377
|
-
case 'openai':
|
|
378
|
-
case 'anthropic':
|
|
379
|
-
return { customEnv: {} };
|
|
380
|
-
default:
|
|
381
|
-
return undefined;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
console.warn(
|
|
386
|
-
'[pi] Passing an object to auth options is deprecated. Use a string mode ("auto" | "openai" | "anthropic" | "custom" | "ai-gateway") instead, and pass credentials via environment variables.',
|
|
387
|
-
);
|
|
388
|
-
return options;
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
function resolveCustomEnv({
|
|
392
|
-
customEnv,
|
|
393
|
-
}: {
|
|
394
|
-
customEnv: Record<string, string>;
|
|
395
|
-
}): Record<string, string> {
|
|
396
|
-
const apiKey = customEnv.AI_GATEWAY_API_KEY;
|
|
397
|
-
if (!apiKey) return {};
|
|
398
|
-
|
|
399
|
-
return {
|
|
400
|
-
AI_GATEWAY_API_KEY: apiKey,
|
|
401
|
-
AI_GATEWAY_BASE_URL:
|
|
402
|
-
customEnv.AI_GATEWAY_BASE_URL ?? DEFAULT_GATEWAY_BASE_URL,
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
|
|
406
454
|
async function registerCustomProviders({
|
|
407
455
|
customEnv,
|
|
408
456
|
registries,
|
package/src/pi-harness.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
import { tool } from '@ai-sdk/provider-utils';
|
|
7
7
|
import type { ExtensionFactory } from '@earendil-works/pi-coding-agent';
|
|
8
8
|
import { z } from 'zod/v4';
|
|
9
|
-
import type {
|
|
9
|
+
import type { PiAuthenticationMode } from './pi-auth';
|
|
10
10
|
import { piResumeStateSchema } from './pi-resume-state';
|
|
11
11
|
import { createPiSession, type PiThinkingLevel } from './pi-session';
|
|
12
12
|
import { VERSION } from './version';
|
|
@@ -22,7 +22,7 @@ const PI_CLIENT_APP = `ai-sdk/harness-pi/${VERSION}`;
|
|
|
22
22
|
*/
|
|
23
23
|
export type PiHarnessSettings = {
|
|
24
24
|
/** Where Pi sources API keys / gateway credentials from. */
|
|
25
|
-
readonly auth?:
|
|
25
|
+
readonly auth?: PiAuthenticationMode;
|
|
26
26
|
/**
|
|
27
27
|
* Pi model id (or name). Leaving this unset falls back to the AI Gateway
|
|
28
28
|
* default when `AI_GATEWAY_API_KEY` / `VERCEL_OIDC_TOKEN` is set, and to
|
package/src/pi-session.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
DefaultResourceLoader,
|
|
4
4
|
defineTool,
|
|
5
5
|
ModelRegistry,
|
|
6
|
-
ModelRuntime,
|
|
7
6
|
SessionManager,
|
|
8
7
|
SettingsManager,
|
|
9
8
|
type AgentSession,
|
|
@@ -37,9 +36,10 @@ import {
|
|
|
37
36
|
} from '@ai-sdk/harness/utils';
|
|
38
37
|
import type { Experimental_SandboxSession as SandboxSession } from '@ai-sdk/provider-utils';
|
|
39
38
|
import {
|
|
39
|
+
createPiModelRuntime,
|
|
40
40
|
registerPiProviders,
|
|
41
41
|
resolvePiEnv,
|
|
42
|
-
type
|
|
42
|
+
type PiAuthenticationMode,
|
|
43
43
|
} from './pi-auth';
|
|
44
44
|
import { getPiTerminalError, parseNativeEvent } from './pi-events';
|
|
45
45
|
import { createPiModelResolver } from './pi-model-resolver';
|
|
@@ -215,7 +215,7 @@ export type PiThinkingLevel =
|
|
|
215
215
|
| 'xhigh';
|
|
216
216
|
|
|
217
217
|
export interface PiSessionSettings {
|
|
218
|
-
readonly auth?:
|
|
218
|
+
readonly auth?: PiAuthenticationMode;
|
|
219
219
|
readonly model?: string;
|
|
220
220
|
readonly thinkingLevel?: PiThinkingLevel;
|
|
221
221
|
readonly mcpServers?: Record<string, unknown>;
|
|
@@ -393,11 +393,16 @@ export async function createPiSession(
|
|
|
393
393
|
// real host filesystem, never in the sandbox/workspace.
|
|
394
394
|
// When `agentDir` is provided, use it instead so the harness can reuse
|
|
395
395
|
// existing CLI logins and model/settings config.
|
|
396
|
+
/*
|
|
397
|
+
* A record-shaped authentication override makes createPiModelRuntime ignore
|
|
398
|
+
* auth.json and models.json because both files can supply credentials from
|
|
399
|
+
* outside that record. General Pi settings still use agentDir below.
|
|
400
|
+
*/
|
|
396
401
|
const agentDir = input.agentDir ?? hostAgentDir;
|
|
397
|
-
const modelRuntime = await
|
|
402
|
+
const modelRuntime = await createPiModelRuntime({
|
|
403
|
+
auth: input.settings.auth,
|
|
398
404
|
authPath: path.join(agentDir, 'auth.json'),
|
|
399
405
|
modelsPath: path.join(agentDir, 'models.json'),
|
|
400
|
-
allowModelNetwork: false,
|
|
401
406
|
});
|
|
402
407
|
const modelRegistry = new ModelRegistry(modelRuntime);
|
|
403
408
|
const settingsManager =
|