@ai-sdk/harness-pi 1.0.63 → 1.0.65
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/README.md +20 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +128 -30
- package/dist/index.js.map +1 -1
- package/package.json +5 -3
- package/src/pi-harness.ts +16 -0
- package/src/pi-session.ts +150 -25
- package/src/pi-translate.ts +26 -2
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.65",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -26,10 +26,12 @@
|
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
+
"@earendil-works/pi-ai": "0.74.2",
|
|
29
30
|
"@earendil-works/pi-coding-agent": "^0.80.10",
|
|
31
|
+
"pi-mcp-adapter": "2.12.1",
|
|
30
32
|
"typebox": "^1.1.38",
|
|
31
|
-
"@ai-sdk/
|
|
32
|
-
"@ai-sdk/
|
|
33
|
+
"@ai-sdk/provider-utils": "5.0.26",
|
|
34
|
+
"@ai-sdk/harness": "1.0.65"
|
|
33
35
|
},
|
|
34
36
|
"peerDependencies": {
|
|
35
37
|
"zod": "^3.25.76 || ^4.1.8"
|
package/src/pi-harness.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
type HarnessV1BuiltinTool,
|
|
5
5
|
} from '@ai-sdk/harness';
|
|
6
6
|
import { tool } from '@ai-sdk/provider-utils';
|
|
7
|
+
import type { ExtensionFactory } from '@earendil-works/pi-coding-agent';
|
|
7
8
|
import { z } from 'zod/v4';
|
|
8
9
|
import type { PiAuthOptions } from './pi-auth';
|
|
9
10
|
import { piResumeStateSchema } from './pi-resume-state';
|
|
@@ -40,6 +41,17 @@ export type PiHarnessSettings = {
|
|
|
40
41
|
* model settings.
|
|
41
42
|
*/
|
|
42
43
|
readonly agentDir?: string;
|
|
44
|
+
/**
|
|
45
|
+
* MCP server definitions keyed by server name. Each definition uses the
|
|
46
|
+
* underlying runtime's native MCP server configuration format.
|
|
47
|
+
*/
|
|
48
|
+
readonly mcpServers?: Record<string, unknown>;
|
|
49
|
+
/**
|
|
50
|
+
* Trusted inline Pi extensions loaded for each harness session.
|
|
51
|
+
*
|
|
52
|
+
* Filesystem-discovered user and project extensions remain disabled.
|
|
53
|
+
*/
|
|
54
|
+
readonly extensionFactories?: ReadonlyArray<ExtensionFactory>;
|
|
43
55
|
};
|
|
44
56
|
|
|
45
57
|
const PI_BUILTIN_TOOLS = {
|
|
@@ -144,6 +156,10 @@ export function createPi(
|
|
|
144
156
|
...(settings.thinkingLevel
|
|
145
157
|
? { thinkingLevel: settings.thinkingLevel }
|
|
146
158
|
: {}),
|
|
159
|
+
...(settings.mcpServers ? { mcpServers: settings.mcpServers } : {}),
|
|
160
|
+
...(settings.extensionFactories
|
|
161
|
+
? { extensionFactories: settings.extensionFactories }
|
|
162
|
+
: {}),
|
|
147
163
|
},
|
|
148
164
|
clientApp: PI_CLIENT_APP,
|
|
149
165
|
isResume: lifecycleState != null,
|
package/src/pi-session.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
SettingsManager,
|
|
9
9
|
type AgentSession,
|
|
10
10
|
type AgentToolResult,
|
|
11
|
+
type ExtensionFactory,
|
|
11
12
|
type Skill,
|
|
12
13
|
type ToolDefinition,
|
|
13
14
|
} from '@earendil-works/pi-coding-agent';
|
|
@@ -63,6 +64,26 @@ import { syncHostWorkspaceFromSandbox } from './pi-workspace-mirror';
|
|
|
63
64
|
|
|
64
65
|
const HARNESS_ID = 'pi';
|
|
65
66
|
|
|
67
|
+
/*
|
|
68
|
+
* pi-mcp-adapter publishes TypeScript source as its package entry point. A
|
|
69
|
+
* non-literal specifier keeps the repository type-check focused on this
|
|
70
|
+
* package's compatibility boundary instead of compiling dependency internals.
|
|
71
|
+
*/
|
|
72
|
+
const PI_MCP_ADAPTER_PACKAGE: string = 'pi-mcp-adapter';
|
|
73
|
+
|
|
74
|
+
type PiMcpAdapterModule = {
|
|
75
|
+
createMcpAdapter(options: {
|
|
76
|
+
config: {
|
|
77
|
+
mcpServers: Record<string, unknown>;
|
|
78
|
+
settings: {
|
|
79
|
+
directTools: boolean;
|
|
80
|
+
toolPrefix: string;
|
|
81
|
+
disableProxyTool: boolean;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
}): ExtensionFactory;
|
|
85
|
+
};
|
|
86
|
+
|
|
66
87
|
/*
|
|
67
88
|
* Pi runs in this Node process, not behind an attachable in-sandbox bridge.
|
|
68
89
|
* During a tool approval pause the Pi turn is still alive and blocked on the
|
|
@@ -192,6 +213,8 @@ export interface PiSessionSettings {
|
|
|
192
213
|
readonly auth?: PiAuthOptions;
|
|
193
214
|
readonly model?: string;
|
|
194
215
|
readonly thinkingLevel?: PiThinkingLevel;
|
|
216
|
+
readonly mcpServers?: Record<string, unknown>;
|
|
217
|
+
readonly extensionFactories?: ReadonlyArray<ExtensionFactory>;
|
|
195
218
|
}
|
|
196
219
|
|
|
197
220
|
export interface CreatePiSessionInput {
|
|
@@ -367,19 +390,69 @@ export async function createPiSession(
|
|
|
367
390
|
// Resolve once: deterministic given the configured model. This is the Pi
|
|
368
391
|
// `Model` object handed to `createAgentSession`.
|
|
369
392
|
const resolvedModel = resolveModel(input.settings.model);
|
|
393
|
+
const mcpServers = resolvePiMcpServers({
|
|
394
|
+
mcpServers: input.settings.mcpServers,
|
|
395
|
+
});
|
|
396
|
+
const hasMcpServers = Object.keys(mcpServers).length > 0;
|
|
370
397
|
|
|
398
|
+
/*
|
|
399
|
+
* Configured MCP servers are served by an inline Pi extension, so they share
|
|
400
|
+
* the extension runtime with the caller-supplied factories: both are loaded
|
|
401
|
+
* by the resource loader below and both are subject to the reload handling
|
|
402
|
+
* that keeps the active runtime alive across resource-only reloads.
|
|
403
|
+
*/
|
|
404
|
+
const extensionFactories: ExtensionFactory[] = [
|
|
405
|
+
...(input.settings.extensionFactories ?? []),
|
|
406
|
+
];
|
|
407
|
+
if (hasMcpServers) {
|
|
408
|
+
const { createMcpAdapter } = (await import(
|
|
409
|
+
PI_MCP_ADAPTER_PACKAGE
|
|
410
|
+
)) as PiMcpAdapterModule;
|
|
411
|
+
extensionFactories.push(
|
|
412
|
+
createMcpAdapter({
|
|
413
|
+
config: {
|
|
414
|
+
mcpServers,
|
|
415
|
+
settings: {
|
|
416
|
+
directTools: true,
|
|
417
|
+
toolPrefix: 'mcp',
|
|
418
|
+
disableProxyTool: true,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
}),
|
|
422
|
+
);
|
|
423
|
+
}
|
|
424
|
+
const hasExtensionFactories = extensionFactories.length > 0;
|
|
425
|
+
let preserveExtensionsResult = false;
|
|
426
|
+
let currentExtensionsResult:
|
|
427
|
+
| ReturnType<DefaultResourceLoader['getExtensions']>
|
|
428
|
+
| undefined;
|
|
371
429
|
const resourceLoader = new DefaultResourceLoader({
|
|
372
430
|
cwd: sessionWorkDir,
|
|
373
431
|
agentDir: hostAgentDir,
|
|
374
432
|
settingsManager,
|
|
375
433
|
appendSystemPromptOverride: () => [],
|
|
376
|
-
extensionFactories
|
|
434
|
+
extensionFactories,
|
|
435
|
+
...(hasExtensionFactories
|
|
436
|
+
? {
|
|
437
|
+
// DefaultResourceLoader invokes inline factories on every reload.
|
|
438
|
+
// Resource-only reloads retain the active extension runtime, while a
|
|
439
|
+
// genuine Pi session rebuild is allowed to replace that runtime.
|
|
440
|
+
extensionsOverride: extensions => {
|
|
441
|
+
if (preserveExtensionsResult && currentExtensionsResult != null) {
|
|
442
|
+
return currentExtensionsResult;
|
|
443
|
+
}
|
|
444
|
+
currentExtensionsResult = extensions;
|
|
445
|
+
return extensions;
|
|
446
|
+
},
|
|
447
|
+
}
|
|
448
|
+
: {}),
|
|
377
449
|
// Pi runs in the host process, so its default resource discovery reaches
|
|
378
450
|
// the host developer's personal config (`~/.pi/agent/*`, `~/.agents/*`).
|
|
379
|
-
// The harness
|
|
380
|
-
// disable
|
|
381
|
-
// developer's personal Pi extensions inside
|
|
382
|
-
//
|
|
451
|
+
// The harness exposes only explicitly supplied inline extension factories;
|
|
452
|
+
// disable filesystem extension discovery entirely to avoid loading and
|
|
453
|
+
// executing a host developer's personal or project Pi extensions inside
|
|
454
|
+
// the server process. Themes and prompt templates stay disabled. Skills
|
|
455
|
+
// are kept but filtered to workspace project skills plus harness-provided
|
|
383
456
|
// skills whose files live in sandbox HOME.
|
|
384
457
|
noExtensions: true,
|
|
385
458
|
noThemes: true,
|
|
@@ -396,6 +469,22 @@ export async function createPiSession(
|
|
|
396
469
|
});
|
|
397
470
|
await resourceLoader.reload();
|
|
398
471
|
|
|
472
|
+
async function reloadResourcesOnly(): Promise<void> {
|
|
473
|
+
if (!hasExtensionFactories) {
|
|
474
|
+
await resourceLoader.reload();
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const factories = extensionFactories.splice(0);
|
|
479
|
+
preserveExtensionsResult = true;
|
|
480
|
+
try {
|
|
481
|
+
await resourceLoader.reload();
|
|
482
|
+
} finally {
|
|
483
|
+
preserveExtensionsResult = false;
|
|
484
|
+
extensionFactories.push(...factories);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
399
488
|
// Per-session mutable state we hold across prompts.
|
|
400
489
|
let piSession: AgentSession | undefined;
|
|
401
490
|
let unsubscribe: (() => void) | undefined;
|
|
@@ -571,20 +660,38 @@ export async function createPiSession(
|
|
|
571
660
|
};
|
|
572
661
|
}
|
|
573
662
|
|
|
663
|
+
async function disposePiSession(): Promise<void> {
|
|
664
|
+
unsubscribe?.();
|
|
665
|
+
unsubscribe = undefined;
|
|
666
|
+
|
|
667
|
+
const session = piSession;
|
|
668
|
+
piSession = undefined;
|
|
669
|
+
if (!session) return;
|
|
670
|
+
|
|
671
|
+
if (hasMcpServers) {
|
|
672
|
+
await session.reload().catch(() => {});
|
|
673
|
+
}
|
|
674
|
+
session.dispose();
|
|
675
|
+
}
|
|
676
|
+
|
|
574
677
|
async function rebuildPiSession(
|
|
575
678
|
userTools: ReadonlyArray<HarnessV1ToolSpec>,
|
|
576
679
|
isFirstBuild: boolean,
|
|
577
|
-
): Promise<
|
|
680
|
+
): Promise<boolean> {
|
|
681
|
+
let resourcesReloaded = false;
|
|
578
682
|
if (piSession) {
|
|
579
|
-
|
|
580
|
-
unsubscribe = undefined;
|
|
581
|
-
piSession.dispose();
|
|
582
|
-
piSession = undefined;
|
|
683
|
+
await disposePiSession();
|
|
583
684
|
// Original adapter waits 25 ms here to let Pi's teardown microtasks
|
|
584
685
|
// settle before the next createAgentSession. Port verbatim.
|
|
585
686
|
// TODO(pi-0.77): verify the race still exists; original SDK had a
|
|
586
687
|
// teardown microtask the host needed to wait on.
|
|
587
688
|
await new Promise(resolve => setTimeout(resolve, 25));
|
|
689
|
+
if (hasExtensionFactories) {
|
|
690
|
+
// dispose() invalidates Pi's current extension runtime, so a replacement
|
|
691
|
+
// AgentSession needs factories to create a fresh runtime before build.
|
|
692
|
+
await resourceLoader.reload();
|
|
693
|
+
resourcesReloaded = true;
|
|
694
|
+
}
|
|
588
695
|
}
|
|
589
696
|
|
|
590
697
|
const { customTools, builtinNames } = buildToolDefinitions(userTools);
|
|
@@ -609,13 +716,18 @@ export async function createPiSession(
|
|
|
609
716
|
settingsManager,
|
|
610
717
|
resourceLoader,
|
|
611
718
|
customTools,
|
|
612
|
-
|
|
719
|
+
...(hasMcpServers
|
|
720
|
+
? { noTools: 'builtin' as const }
|
|
721
|
+
: { tools: toolNames }),
|
|
613
722
|
...(input.settings.thinkingLevel
|
|
614
723
|
? { thinkingLevel: input.settings.thinkingLevel }
|
|
615
724
|
: {}),
|
|
616
725
|
...(resolvedModel ? { model: resolvedModel } : {}),
|
|
617
726
|
});
|
|
618
727
|
piSession = session;
|
|
728
|
+
if (hasMcpServers) {
|
|
729
|
+
await piSession.bindExtensions({ mode: 'print' });
|
|
730
|
+
}
|
|
619
731
|
|
|
620
732
|
// Pick up the actual session file path so doStop can persist it. Pi
|
|
621
733
|
// 0.77 emits `.jsonl` files; older builds used `.json`. Persist the
|
|
@@ -628,6 +740,7 @@ export async function createPiSession(
|
|
|
628
740
|
|
|
629
741
|
translatorState = createPiTranslatorState({
|
|
630
742
|
builtinToolNames: builtinNames,
|
|
743
|
+
hostToolNames: userTools.map(tool => tool.name),
|
|
631
744
|
nativeToCommon: NATIVE_TO_COMMON,
|
|
632
745
|
});
|
|
633
746
|
|
|
@@ -645,6 +758,7 @@ export async function createPiSession(
|
|
|
645
758
|
// Other event types outside a turn have no consumer and are dropped.
|
|
646
759
|
}
|
|
647
760
|
});
|
|
761
|
+
return resourcesReloaded;
|
|
648
762
|
}
|
|
649
763
|
|
|
650
764
|
/*
|
|
@@ -665,12 +779,15 @@ export async function createPiSession(
|
|
|
665
779
|
const userTools = turnOpts.tools;
|
|
666
780
|
const signature = JSON.stringify(userTools.map(t => t.name).sort());
|
|
667
781
|
const needsRebuild = piSession == null || signature !== lastToolsSignature;
|
|
782
|
+
let resourcesReloaded = false;
|
|
668
783
|
if (needsRebuild) {
|
|
669
|
-
await rebuildPiSession(userTools, piSession == null);
|
|
784
|
+
resourcesReloaded = await rebuildPiSession(userTools, piSession == null);
|
|
670
785
|
lastToolsSignature = signature;
|
|
671
786
|
}
|
|
672
787
|
|
|
673
|
-
|
|
788
|
+
if (!resourcesReloaded) {
|
|
789
|
+
await reloadResourcesOnly();
|
|
790
|
+
}
|
|
674
791
|
await syncHostWorkspaceFromSandbox({
|
|
675
792
|
sandbox,
|
|
676
793
|
sandboxWorkDir: input.sessionWorkDir,
|
|
@@ -682,6 +799,7 @@ export async function createPiSession(
|
|
|
682
799
|
// session was built with.
|
|
683
800
|
translatorState = createPiTranslatorState({
|
|
684
801
|
builtinToolNames: [...PI_NATIVE_BUILTIN_NAMES],
|
|
802
|
+
hostToolNames: userTools.map(tool => tool.name),
|
|
685
803
|
nativeToCommon: NATIVE_TO_COMMON,
|
|
686
804
|
});
|
|
687
805
|
|
|
@@ -795,10 +913,7 @@ export async function createPiSession(
|
|
|
795
913
|
}
|
|
796
914
|
}
|
|
797
915
|
|
|
798
|
-
|
|
799
|
-
unsubscribe = undefined;
|
|
800
|
-
piSession?.dispose();
|
|
801
|
-
piSession = undefined;
|
|
916
|
+
await disposePiSession();
|
|
802
917
|
workspaceVfs.unmount();
|
|
803
918
|
await rm(hostRoot, { recursive: true, force: true });
|
|
804
919
|
|
|
@@ -884,10 +999,7 @@ export async function createPiSession(
|
|
|
884
999
|
parkedPiSessions.delete(input.sessionId);
|
|
885
1000
|
settlePendingToolResults('Pi session stopped');
|
|
886
1001
|
settlePendingToolApprovals('Pi session stopped');
|
|
887
|
-
|
|
888
|
-
unsubscribe = undefined;
|
|
889
|
-
piSession?.dispose();
|
|
890
|
-
piSession = undefined;
|
|
1002
|
+
await disposePiSession();
|
|
891
1003
|
workspaceVfs.unmount();
|
|
892
1004
|
await rm(hostRoot, { recursive: true, force: true });
|
|
893
1005
|
},
|
|
@@ -972,10 +1084,7 @@ export async function createPiSession(
|
|
|
972
1084
|
parkedPiSessions.delete(input.sessionId);
|
|
973
1085
|
settlePendingToolResults('Pi session suspended');
|
|
974
1086
|
settlePendingToolApprovals('Pi session suspended');
|
|
975
|
-
|
|
976
|
-
unsubscribe = undefined;
|
|
977
|
-
piSession?.dispose();
|
|
978
|
-
piSession = undefined;
|
|
1087
|
+
await disposePiSession();
|
|
979
1088
|
workspaceVfs.unmount();
|
|
980
1089
|
await rm(hostRoot, { recursive: true, force: true });
|
|
981
1090
|
|
|
@@ -991,6 +1100,22 @@ export async function createPiSession(
|
|
|
991
1100
|
return sessionImpl;
|
|
992
1101
|
}
|
|
993
1102
|
|
|
1103
|
+
function resolvePiMcpServers({
|
|
1104
|
+
mcpServers,
|
|
1105
|
+
}: {
|
|
1106
|
+
mcpServers: Record<string, unknown> | undefined;
|
|
1107
|
+
}): Record<string, unknown> {
|
|
1108
|
+
if (mcpServers == null) return {};
|
|
1109
|
+
for (const [name, value] of Object.entries(mcpServers)) {
|
|
1110
|
+
if (value == null || typeof value !== 'object' || Array.isArray(value)) {
|
|
1111
|
+
throw new Error(
|
|
1112
|
+
`Pi MCP server ${JSON.stringify(name)} must be configured with an object value.`,
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return mcpServers;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
994
1119
|
/**
|
|
995
1120
|
* Whether a terminal error (string from Pi's event stream, or a thrown error)
|
|
996
1121
|
* is an abort — the expected result of `doSuspendTurn` aborting the in-flight
|
package/src/pi-translate.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { randomBytes } from 'node:crypto';
|
|
2
2
|
import type { HarnessV1StreamPart } from '@ai-sdk/harness';
|
|
3
|
+
import { secureJsonParse } from '@ai-sdk/provider-utils';
|
|
3
4
|
import { extractAssistantText, type PiSessionEvent } from './pi-events';
|
|
4
5
|
import { serializeToolOutput } from './pi-utils';
|
|
5
6
|
|
|
@@ -43,6 +44,7 @@ export interface PiTranslatorState {
|
|
|
43
44
|
* the matching `tool_result`/`tool_execution_end` event is translated.
|
|
44
45
|
*/
|
|
45
46
|
hostToolResults: Map<string, unknown>;
|
|
47
|
+
dynamicToolCallIds: Set<string>;
|
|
46
48
|
/**
|
|
47
49
|
* Names of tools that Pi executes natively (read/write/edit/bash/grep/
|
|
48
50
|
* find/ls). `tool-call` events for these get `providerExecuted: true`
|
|
@@ -50,6 +52,7 @@ export interface PiTranslatorState {
|
|
|
50
52
|
* tools are not in this set.
|
|
51
53
|
*/
|
|
52
54
|
readonly builtinToolNames: ReadonlySet<string>;
|
|
55
|
+
readonly hostToolNames: ReadonlySet<string>;
|
|
53
56
|
/**
|
|
54
57
|
* Map of native tool name → common name. `find` → `glob`, etc. Pi emits
|
|
55
58
|
* native names on its events; the wire `toolName` is the common name when
|
|
@@ -60,6 +63,7 @@ export interface PiTranslatorState {
|
|
|
60
63
|
|
|
61
64
|
export interface PiTranslatorStateOptions {
|
|
62
65
|
readonly builtinToolNames?: ReadonlyArray<string>;
|
|
66
|
+
readonly hostToolNames?: ReadonlyArray<string>;
|
|
63
67
|
readonly nativeToCommon?:
|
|
64
68
|
| ReadonlyMap<string, string>
|
|
65
69
|
| Record<string, string>;
|
|
@@ -82,7 +86,9 @@ export function createPiTranslatorState(
|
|
|
82
86
|
pendingStepToolCallIds: new Set(),
|
|
83
87
|
stepOpen: false,
|
|
84
88
|
hostToolResults: new Map(),
|
|
89
|
+
dynamicToolCallIds: new Set(),
|
|
85
90
|
builtinToolNames: new Set(options.builtinToolNames ?? []),
|
|
91
|
+
hostToolNames: new Set(options.hostToolNames ?? []),
|
|
86
92
|
nativeToCommonNameMap: map,
|
|
87
93
|
};
|
|
88
94
|
}
|
|
@@ -128,6 +134,15 @@ function unwrapPiToolResult(event: PiSessionEvent): never {
|
|
|
128
134
|
return (event.result ?? event.content ?? null) as never;
|
|
129
135
|
}
|
|
130
136
|
|
|
137
|
+
function parseMcpToolResult(content: unknown): unknown {
|
|
138
|
+
if (typeof content !== 'string') return content;
|
|
139
|
+
try {
|
|
140
|
+
return secureJsonParse(content);
|
|
141
|
+
} catch {
|
|
142
|
+
return content;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
131
146
|
function resolveToolName(
|
|
132
147
|
state: PiTranslatorState,
|
|
133
148
|
nativeName: string,
|
|
@@ -307,7 +322,11 @@ export function translatePiEvent(
|
|
|
307
322
|
if (!event.toolCallId || !event.toolName) return [];
|
|
308
323
|
const { wire, native } = resolveToolName(state, event.toolName);
|
|
309
324
|
state.observedToolNames.set(event.toolCallId, wire);
|
|
310
|
-
const
|
|
325
|
+
const isMcpTool =
|
|
326
|
+
!state.hostToolNames.has(native) &&
|
|
327
|
+
(native === 'mcp' || native.startsWith('mcp__'));
|
|
328
|
+
const providerExecuted = state.builtinToolNames.has(native) || isMcpTool;
|
|
329
|
+
if (isMcpTool) state.dynamicToolCallIds.add(event.toolCallId);
|
|
311
330
|
const input = serializeToolOutput(event.args ?? event.input ?? {});
|
|
312
331
|
return [
|
|
313
332
|
{
|
|
@@ -317,6 +336,7 @@ export function translatePiEvent(
|
|
|
317
336
|
input,
|
|
318
337
|
...(wire !== native ? { nativeName: native } : {}),
|
|
319
338
|
...(providerExecuted ? { providerExecuted: true } : {}),
|
|
339
|
+
...(isMcpTool ? { dynamic: true } : {}),
|
|
320
340
|
} as HarnessV1StreamPart,
|
|
321
341
|
];
|
|
322
342
|
}
|
|
@@ -330,6 +350,7 @@ export function translatePiEvent(
|
|
|
330
350
|
recordedName ??
|
|
331
351
|
(nativeName ? resolveToolName(state, nativeName).wire : undefined);
|
|
332
352
|
if (!wire) return [];
|
|
353
|
+
const dynamic = state.dynamicToolCallIds.delete(event.toolCallId);
|
|
333
354
|
/*
|
|
334
355
|
* Prefer the exact value the host submitted for user-registered tools
|
|
335
356
|
* (see `hostToolResults`). Built-in tools, whose results Pi produces and
|
|
@@ -341,7 +362,9 @@ export function translatePiEvent(
|
|
|
341
362
|
HarnessV1StreamPart,
|
|
342
363
|
{ type: 'tool-result' }
|
|
343
364
|
>['result'])
|
|
344
|
-
:
|
|
365
|
+
: dynamic
|
|
366
|
+
? parseMcpToolResult(unwrapPiToolResult(event))
|
|
367
|
+
: unwrapPiToolResult(event);
|
|
345
368
|
state.hostToolResults.delete(event.toolCallId);
|
|
346
369
|
state.pendingStepToolCallIds.delete(event.toolCallId);
|
|
347
370
|
return [
|
|
@@ -351,6 +374,7 @@ export function translatePiEvent(
|
|
|
351
374
|
toolName: wire,
|
|
352
375
|
result,
|
|
353
376
|
...(event.isError ? { isError: true } : {}),
|
|
377
|
+
...(dynamic ? { dynamic: true } : {}),
|
|
354
378
|
} as HarnessV1StreamPart,
|
|
355
379
|
...finishStep(state),
|
|
356
380
|
];
|