@ai-sdk/harness-pi 1.0.65 → 1.0.67
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.js +33 -21
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/pi-model-resolver.ts +40 -5
- package/src/pi-session.ts +17 -14
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.67",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"@earendil-works/pi-coding-agent": "^0.80.10",
|
|
31
31
|
"pi-mcp-adapter": "2.12.1",
|
|
32
32
|
"typebox": "^1.1.38",
|
|
33
|
-
"@ai-sdk/
|
|
34
|
-
"@ai-sdk/
|
|
33
|
+
"@ai-sdk/harness": "1.0.66",
|
|
34
|
+
"@ai-sdk/provider-utils": "5.0.26"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"zod": "^3.25.76 || ^4.1.8"
|
package/src/pi-model-resolver.ts
CHANGED
|
@@ -11,6 +11,30 @@ 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
|
+
/*
|
|
15
|
+
* A `"<provider>/<id>"` reference normally resolves under that provider, not
|
|
16
|
+
* any entry whose flat `id` happens to equal the whole string. Routing proxies
|
|
17
|
+
* (`vercel-ai-gateway`, `openrouter`) can carry another provider's id verbatim
|
|
18
|
+
* as their own flat `id` (e.g. `id: "xai/grok-4.3"`), so this tier normally
|
|
19
|
+
* wins over flat matching. An authenticated literal-id match remains usable
|
|
20
|
+
* when the scoped provider is unauthenticated, matching Pi's own resolver.
|
|
21
|
+
* Gateway preference stays first because gateway credentials explicitly opt
|
|
22
|
+
* the harness into routing matching models through the gateway.
|
|
23
|
+
*/
|
|
24
|
+
const findScopedMatch = (
|
|
25
|
+
effectiveId: string,
|
|
26
|
+
models: PiModel[],
|
|
27
|
+
): PiModel | undefined => {
|
|
28
|
+
const slashIndex = effectiveId.indexOf('/');
|
|
29
|
+
if (slashIndex === -1) return undefined;
|
|
30
|
+
|
|
31
|
+
const prefix = effectiveId.slice(0, slashIndex);
|
|
32
|
+
const bareId = effectiveId.slice(slashIndex + 1);
|
|
33
|
+
return models.find(
|
|
34
|
+
m => m.provider === prefix && (m.id === bareId || m.name === bareId),
|
|
35
|
+
);
|
|
36
|
+
};
|
|
37
|
+
|
|
14
38
|
export function createPiModelResolver({
|
|
15
39
|
modelRegistry,
|
|
16
40
|
env = process.env,
|
|
@@ -47,10 +71,21 @@ export function createPiModelResolver({
|
|
|
47
71
|
// (e.g. `anthropic/claude-sonnet-4.6` exists under both `openrouter` and
|
|
48
72
|
// `vercel-ai-gateway`); without this preference Pi would dispatch through
|
|
49
73
|
// a provider we didn't register, which fails with "No API key found".
|
|
50
|
-
|
|
51
|
-
(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
74
|
+
const gatewayMatch = useGateway
|
|
75
|
+
? models.find(m => m.provider === 'vercel-ai-gateway' && matches(m))
|
|
76
|
+
: undefined;
|
|
77
|
+
if (gatewayMatch) return gatewayMatch;
|
|
78
|
+
|
|
79
|
+
const scopedMatch = findScopedMatch(effectiveId, models);
|
|
80
|
+
if (scopedMatch && !modelRegistry.hasConfiguredAuth(scopedMatch)) {
|
|
81
|
+
const authenticatedFlatMatches = models.filter(
|
|
82
|
+
m => m.id === effectiveId && modelRegistry.hasConfiguredAuth(m),
|
|
83
|
+
);
|
|
84
|
+
if (authenticatedFlatMatches.length === 1) {
|
|
85
|
+
return authenticatedFlatMatches[0];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return scopedMatch ?? models.find(matches);
|
|
55
90
|
};
|
|
56
91
|
}
|
package/src/pi-session.ts
CHANGED
|
@@ -55,7 +55,6 @@ import {
|
|
|
55
55
|
import { toolSpecToTypeBoxParameters } from './pi-typebox-adapter';
|
|
56
56
|
import {
|
|
57
57
|
extractUserText,
|
|
58
|
-
frameInstructions,
|
|
59
58
|
safePiMetadataSegment,
|
|
60
59
|
serializeToolOutput,
|
|
61
60
|
} from './pi-utils';
|
|
@@ -395,6 +394,8 @@ export async function createPiSession(
|
|
|
395
394
|
});
|
|
396
395
|
const hasMcpServers = Object.keys(mcpServers).length > 0;
|
|
397
396
|
|
|
397
|
+
let sessionInstructions: string | undefined;
|
|
398
|
+
|
|
398
399
|
/*
|
|
399
400
|
* Configured MCP servers are served by an inline Pi extension, so they share
|
|
400
401
|
* the extension runtime with the caller-supplied factories: both are loaded
|
|
@@ -426,11 +427,13 @@ export async function createPiSession(
|
|
|
426
427
|
let currentExtensionsResult:
|
|
427
428
|
| ReturnType<DefaultResourceLoader['getExtensions']>
|
|
428
429
|
| undefined;
|
|
430
|
+
|
|
429
431
|
const resourceLoader = new DefaultResourceLoader({
|
|
430
432
|
cwd: sessionWorkDir,
|
|
431
433
|
agentDir: hostAgentDir,
|
|
432
434
|
settingsManager,
|
|
433
|
-
appendSystemPromptOverride: () =>
|
|
435
|
+
appendSystemPromptOverride: () =>
|
|
436
|
+
sessionInstructions ? [sessionInstructions] : [],
|
|
434
437
|
extensionFactories,
|
|
435
438
|
...(hasExtensionFactories
|
|
436
439
|
? {
|
|
@@ -498,12 +501,6 @@ export async function createPiSession(
|
|
|
498
501
|
* from the persisted journal.
|
|
499
502
|
*/
|
|
500
503
|
let suspending = false;
|
|
501
|
-
/*
|
|
502
|
-
* Instructions are prepended to the first user message of a fresh session
|
|
503
|
-
* only. A resumed session already carried them in its original first
|
|
504
|
-
* message (preserved in the persisted session file), so it starts "applied".
|
|
505
|
-
*/
|
|
506
|
-
let instructionsApplied = input.isResume;
|
|
507
504
|
const pendingToolResults = new Map<string, PendingToolResult>();
|
|
508
505
|
const pendingToolApprovals = new Map<string, PendingToolApproval>();
|
|
509
506
|
|
|
@@ -521,6 +518,15 @@ export async function createPiSession(
|
|
|
521
518
|
*/
|
|
522
519
|
const pendingCompactionParts: HarnessV1StreamPart[] = [];
|
|
523
520
|
|
|
521
|
+
async function applySessionInstructions(
|
|
522
|
+
instructions: string | undefined,
|
|
523
|
+
): Promise<void> {
|
|
524
|
+
if (instructions === sessionInstructions) return;
|
|
525
|
+
sessionInstructions = instructions;
|
|
526
|
+
await reloadResourcesOnly();
|
|
527
|
+
piSession?.setActiveToolsByName(piSession.getActiveToolNames());
|
|
528
|
+
}
|
|
529
|
+
|
|
524
530
|
const remoteOps = createPiRemoteOps({
|
|
525
531
|
sandbox,
|
|
526
532
|
paths,
|
|
@@ -939,14 +945,10 @@ export async function createPiSession(
|
|
|
939
945
|
doPromptTurn: async (
|
|
940
946
|
promptOpts: HarnessV1PromptTurnOptions,
|
|
941
947
|
): Promise<HarnessV1PromptControl> => {
|
|
942
|
-
|
|
943
|
-
if (!instructionsApplied && promptOpts.instructions) {
|
|
944
|
-
text = frameInstructions(promptOpts.instructions, text);
|
|
945
|
-
}
|
|
946
|
-
instructionsApplied = true;
|
|
948
|
+
await applySessionInstructions(promptOpts.instructions);
|
|
947
949
|
|
|
948
950
|
return runTurn({
|
|
949
|
-
text,
|
|
951
|
+
text: extractUserText(promptOpts.prompt),
|
|
950
952
|
tools: promptOpts.tools ?? [],
|
|
951
953
|
emit: promptOpts.emit,
|
|
952
954
|
abortSignal: promptOpts.abortSignal,
|
|
@@ -972,6 +974,7 @@ export async function createPiSession(
|
|
|
972
974
|
* flight at the slice boundary is recomputed because a host-resident
|
|
973
975
|
* runtime cannot do a lossless attach.
|
|
974
976
|
*/
|
|
977
|
+
await applySessionInstructions(continueOpts.instructions);
|
|
975
978
|
return runTurn({
|
|
976
979
|
text: '',
|
|
977
980
|
tools: continueOpts.tools ?? [],
|