@modelprofile.com/flexharness 10.13.0 → 10.15.0
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/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/classes.codesandbox.d.ts +39 -0
- package/dist_ts/classes.codesandbox.js +539 -0
- package/dist_ts/classes.flexharness.d.ts +10 -0
- package/dist_ts/classes.flexharness.js +148 -16
- package/dist_ts/classes.toolcatalog.d.ts +27 -7
- package/dist_ts/classes.toolcatalog.js +311 -69
- package/dist_ts/interfaces.d.ts +137 -5
- package/dist_ts/interfaces.js +32 -1
- package/dist_ts/plugins.d.ts +62 -1
- package/dist_ts/plugins.js +15 -1
- package/dist_ts/utils.harnessoptions.d.ts +9 -2
- package/dist_ts/utils.harnessoptions.js +52 -11
- package/dist_ts/utils.json.js +3 -1
- package/dist_ts/utils.tooloutput.d.ts +2 -0
- package/dist_ts/utils.tooloutput.js +11 -1
- package/dist_ts_compaction/plugins.d.ts +3 -3
- package/dist_ts_compaction/plugins.js +3 -3
- package/dist_ts_compaction/smartagent.compaction.d.ts +10 -1
- package/dist_ts_compaction/smartagent.compaction.js +79 -38
- package/package.json +16 -4
- package/readme.md +1 -1
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/classes.codesandbox.ts +565 -0
- package/ts/classes.flexharness.ts +152 -15
- package/ts/classes.toolcatalog.ts +344 -66
- package/ts/interfaces.ts +169 -5
- package/ts/plugins.ts +79 -0
- package/ts/readme.md +1 -1
- package/ts/utils.harnessoptions.ts +68 -12
- package/ts/utils.json.ts +2 -0
- package/ts/utils.tooloutput.ts +9 -0
- package/ts_compaction/plugins.ts +3 -3
- package/ts_compaction/smartagent.compaction.ts +90 -34
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
FLEX_REVERSION_REFERENCE_MAX_BYTES,
|
|
29
29
|
FLEX_REVERSION_WORKSPACE_ID_MAX_BYTES,
|
|
30
30
|
FLEX_REVERSION_WORKSPACE_LABEL_MAX_BYTES,
|
|
31
|
+
FLEX_CODE_MODE_TOOL_NAME,
|
|
31
32
|
FLEX_SESSION_IMPORT_MAX_DEPTH,
|
|
32
33
|
FLEX_TOOL_CATALOG_TOOL_NAMES,
|
|
33
34
|
} from './interfaces.js';
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
assertNoCatalogCollisions,
|
|
37
38
|
catalogToolInputSchema,
|
|
38
39
|
} from './classes.toolcatalog.js';
|
|
40
|
+
import { FlexCodeSandbox, type IFlexCodeHost } from './classes.codesandbox.js';
|
|
39
41
|
import type {
|
|
40
42
|
IFlexAgentContextCompactionOptions,
|
|
41
43
|
IFlexAgentContextInvocation,
|
|
@@ -119,6 +121,7 @@ import type {
|
|
|
119
121
|
IFlexSubagentProvenance,
|
|
120
122
|
IFlexTerminalProjection,
|
|
121
123
|
IFlexCatalogDescribeInput,
|
|
124
|
+
IFlexCatalogProposal,
|
|
122
125
|
IFlexCatalogSearchInput,
|
|
123
126
|
IFlexToolCatalogOptions,
|
|
124
127
|
IFlexToolHandle,
|
|
@@ -222,7 +225,7 @@ import {
|
|
|
222
225
|
validateSessionImportHandle,
|
|
223
226
|
type INormalizedSessionImportOptions,
|
|
224
227
|
} from './utils.sessionimport.js';
|
|
225
|
-
import { wrapToolSet } from './utils.tooloutput.js';
|
|
228
|
+
import { settleToolOutput, wrapToolSet } from './utils.tooloutput.js';
|
|
226
229
|
import {
|
|
227
230
|
validateCompactSessionOptions,
|
|
228
231
|
validateCreateProjectTaskInput,
|
|
@@ -445,6 +448,8 @@ interface IActiveRun {
|
|
|
445
448
|
toolProviderContext?: Readonly<IFlexToolProviderContext<unknown>>;
|
|
446
449
|
/** The catalog methods the run offers through search_tools, describe_tools and execute. */
|
|
447
450
|
toolCatalog?: FlexToolCatalog<unknown>;
|
|
451
|
+
/** The sandbox `run_code` programs run in, loaded when the catalog has code mode. */
|
|
452
|
+
codeSandbox?: FlexCodeSandbox;
|
|
448
453
|
subagents: ReadonlyMap<string, Readonly<IFlexSubagentDefinition>>;
|
|
449
454
|
allowedTools?: readonly string[];
|
|
450
455
|
agentAllowedTools?: readonly string[];
|
|
@@ -865,7 +870,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
865
870
|
private readonly externalErrorProjector?: TFlexExternalErrorProjector;
|
|
866
871
|
private readonly subagents: ReadonlyMap<string, Readonly<IFlexSubagentDefinition>>;
|
|
867
872
|
private readonly builtInTools: INormalizedBuiltInTools;
|
|
868
|
-
private readonly toolCatalogOptions: Readonly<
|
|
873
|
+
private readonly toolCatalogOptions: Readonly<IFlexToolCatalogOptions>;
|
|
869
874
|
private readonly slashCommands: ReadonlyMap<string, TRegisteredSlashCommand<TScope>>;
|
|
870
875
|
private readonly maxSubagentDepth: number;
|
|
871
876
|
private readonly maxSubagentCallsPerRun: number;
|
|
@@ -952,7 +957,13 @@ export class FlexHarness<TScope = unknown> {
|
|
|
952
957
|
this.externalErrorProjector = options.externalErrorProjector;
|
|
953
958
|
this.subagents = normalizeSubagents(options.subagents);
|
|
954
959
|
this.builtInTools = normalizeBuiltInTools(options.builtInTools);
|
|
955
|
-
|
|
960
|
+
const toolCatalogOptions = normalizeToolCatalogOptions(options.toolCatalog);
|
|
961
|
+
this.toolCatalogOptions = Object.freeze({
|
|
962
|
+
declarations: toolCatalogOptions.declarations,
|
|
963
|
+
outputTypeDepth: toolCatalogOptions.outputTypeDepth,
|
|
964
|
+
describeTopMatch: toolCatalogOptions.describeTopMatch,
|
|
965
|
+
...(toolCatalogOptions.codeMode ? { codeMode: { limits: toolCatalogOptions.codeMode } } : {}),
|
|
966
|
+
});
|
|
956
967
|
this.slashCommands = normalizeSlashCommands(options.slashCommands);
|
|
957
968
|
this.maxSubagentDepth = resolveBoundedPositiveInteger(
|
|
958
969
|
options.maxSubagentDepth,
|
|
@@ -5844,6 +5855,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
5844
5855
|
if (toolHandle?.catalog) {
|
|
5845
5856
|
try {
|
|
5846
5857
|
catalog = await FlexToolCatalog.create(toolHandle.catalog, this.toolCatalogOptions);
|
|
5858
|
+
if (catalog.codeMode) run.codeSandbox = await FlexCodeSandbox.load();
|
|
5847
5859
|
} catch (error) {
|
|
5848
5860
|
throw this.projectExternalError(run, error, 'toolProvider');
|
|
5849
5861
|
}
|
|
@@ -5962,7 +5974,8 @@ export class FlexHarness<TScope = unknown> {
|
|
|
5962
5974
|
description: definitions[names.search].description,
|
|
5963
5975
|
strict: false,
|
|
5964
5976
|
inputSchema: catalogToolInputSchema<IFlexCatalogSearchInput>(definitions[names.search]),
|
|
5965
|
-
execute: async (input: IFlexCatalogSearchInput
|
|
5977
|
+
execute: async (input: IFlexCatalogSearchInput, options?: { messages?: plugins.ModelMessage[] }) =>
|
|
5978
|
+
catalog.search(input, { declaredTypes: catalog.declaredTypesIn(options?.messages), maxBytes: describeBytes }),
|
|
5966
5979
|
}),
|
|
5967
5980
|
[names.describe]: plugins.tool({
|
|
5968
5981
|
description: definitions[names.describe].description,
|
|
@@ -5984,20 +5997,141 @@ export class FlexHarness<TScope = unknown> {
|
|
|
5984
5997
|
input: { method: string; params?: Record<string, unknown> },
|
|
5985
5998
|
options?: { toolCallId?: string; abortSignal?: AbortSignal },
|
|
5986
5999
|
) => {
|
|
6000
|
+
const toolCallId = options?.toolCallId ?? '';
|
|
6001
|
+
const abortSignal = options?.abortSignal ?? run.controller.signal;
|
|
5987
6002
|
const call = await catalog.prepareCall(input.method, input.params);
|
|
5988
|
-
if (!call.success)
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
6003
|
+
if (!call.success) throw this.catalogCallError(run, toolCallId, call.error);
|
|
6004
|
+
const context = { method: call.method, toolCallId, abortSignal, provider: run.toolProviderContext! };
|
|
6005
|
+
const decision = await call.decide(context);
|
|
6006
|
+
if (decision === 'run') return call.run(context);
|
|
6007
|
+
if (decision === 'propose') {
|
|
6008
|
+
return catalog.propose(
|
|
6009
|
+
[{ id: toolCallId, method: call.method, params: call.params }],
|
|
6010
|
+
{ toolCallId, abortSignal, provider: run.toolProviderContext! },
|
|
6011
|
+
);
|
|
5992
6012
|
}
|
|
5993
|
-
|
|
5994
|
-
method: call.method,
|
|
5995
|
-
toolCallId: options?.toolCallId ?? '',
|
|
5996
|
-
abortSignal: options?.abortSignal ?? run.controller.signal,
|
|
5997
|
-
provider: run.toolProviderContext!,
|
|
5998
|
-
});
|
|
6013
|
+
throw this.catalogCallError(run, toolCallId, { error: 'denied', method: call.method, reason: decision.deny });
|
|
5999
6014
|
},
|
|
6000
6015
|
}),
|
|
6016
|
+
...(catalog.codeMode && run.codeSandbox ? {
|
|
6017
|
+
[FLEX_CODE_MODE_TOOL_NAME]: plugins.tool({
|
|
6018
|
+
description: definitions[FLEX_CODE_MODE_TOOL_NAME].description,
|
|
6019
|
+
strict: false,
|
|
6020
|
+
inputSchema: catalogToolInputSchema<{ code: string }>(definitions[FLEX_CODE_MODE_TOOL_NAME]),
|
|
6021
|
+
execute: async (input: { code: string }, options?: { toolCallId?: string; abortSignal?: AbortSignal }) =>
|
|
6022
|
+
this.runCatalogProgram(run, catalog, input.code, options?.toolCallId ?? '', options?.abortSignal ?? run.controller.signal),
|
|
6023
|
+
}),
|
|
6024
|
+
} : {}),
|
|
6025
|
+
};
|
|
6026
|
+
}
|
|
6027
|
+
|
|
6028
|
+
/** A refused catalog call as the trusted error the model and the tool part show. */
|
|
6029
|
+
private catalogCallError(run: IActiveRun, toolCallId: string, payload: unknown): Error {
|
|
6030
|
+
const error = this.trustInternalError(new FlexHarnessValidationError(JSON.stringify(payload)));
|
|
6031
|
+
if (toolCallId) run.trustedToolExecutionErrors.set(toolCallId, error);
|
|
6032
|
+
return error;
|
|
6033
|
+
}
|
|
6034
|
+
|
|
6035
|
+
/**
|
|
6036
|
+
* Runs a `run_code` program. Every catalog call it makes is dispatched like an `execute`
|
|
6037
|
+
* (validation, `onCall`, the method's run with its permission requests, the output limits, error
|
|
6038
|
+
* projection) and recorded as a tool part of its own, named by the method, with the `run_code`
|
|
6039
|
+
* call as its parent. Proposals go to `onProposals` as one batch once the program finished; a
|
|
6040
|
+
* program that fails proposes nothing.
|
|
6041
|
+
*/
|
|
6042
|
+
private async runCatalogProgram(
|
|
6043
|
+
run: IActiveRun,
|
|
6044
|
+
catalog: FlexToolCatalog<unknown>,
|
|
6045
|
+
code: string,
|
|
6046
|
+
toolCallId: string,
|
|
6047
|
+
abortSignal: AbortSignal,
|
|
6048
|
+
): Promise<unknown> {
|
|
6049
|
+
const proposals: IFlexCatalogProposal[] = [];
|
|
6050
|
+
const base = run.toolProviderContext!;
|
|
6051
|
+
// Waiting for a permission decision is the user's time, not the program's.
|
|
6052
|
+
let waitedMs = 0;
|
|
6053
|
+
let waiting = 0;
|
|
6054
|
+
let waitingSince = 0;
|
|
6055
|
+
/** The provider context of one call: its permission waits are timed, and end when the program stops. */
|
|
6056
|
+
const providerFor = (signal: AbortSignal): Readonly<IFlexToolProviderContext<unknown>> => Object.freeze({
|
|
6057
|
+
...base,
|
|
6058
|
+
requestPermission: async (request: IFlexPermissionRequestInput) => {
|
|
6059
|
+
signal.throwIfAborted();
|
|
6060
|
+
if (waiting++ === 0) waitingSince = performance.now();
|
|
6061
|
+
let onAbort: (() => void) | undefined;
|
|
6062
|
+
try {
|
|
6063
|
+
await Promise.race([
|
|
6064
|
+
base.requestPermission(request),
|
|
6065
|
+
new Promise<never>((_resolve, reject) => {
|
|
6066
|
+
onAbort = () => reject(signal.reason);
|
|
6067
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
6068
|
+
}),
|
|
6069
|
+
]);
|
|
6070
|
+
} finally {
|
|
6071
|
+
if (onAbort) signal.removeEventListener('abort', onAbort);
|
|
6072
|
+
if (--waiting === 0) waitedMs += performance.now() - waitingSince;
|
|
6073
|
+
}
|
|
6074
|
+
},
|
|
6075
|
+
});
|
|
6076
|
+
const stopped = (method: string) => ({ ok: false as const, error: { error: 'aborted', method } });
|
|
6077
|
+
let callNumber = 0;
|
|
6078
|
+
const finish = (id: string, method: string, input: unknown, outcome: { output: unknown } | { error: Error }) => {
|
|
6079
|
+
if ('error' in outcome) run.trustedToolExecutionErrors.set(id, outcome.error);
|
|
6080
|
+
this.onToolFinish(run.state, run.sessionId, 'error' in outcome
|
|
6081
|
+
? { toolCallId: id, toolName: method, input, success: false, error: outcome.error.message }
|
|
6082
|
+
: { toolCallId: id, toolName: method, input, success: true, output: outcome.output });
|
|
6083
|
+
};
|
|
6084
|
+
const host: IFlexCodeHost = {
|
|
6085
|
+
waitedMs: () => waitedMs + (waiting > 0 ? performance.now() - waitingSince : 0),
|
|
6086
|
+
call: async (method, params, signal) => {
|
|
6087
|
+
if (signal.aborted) return stopped(method);
|
|
6088
|
+
const id = `${toolCallId}.${++callNumber}`;
|
|
6089
|
+
const call = await catalog.prepareCall(method, params);
|
|
6090
|
+
if (!call.success) {
|
|
6091
|
+
if (catalog.has(method)) {
|
|
6092
|
+
this.onToolStart(run.state, run.sessionId, { toolCallId: id, toolName: method, input: params ?? {}, parentToolCallId: toolCallId });
|
|
6093
|
+
finish(id, method, params, { error: this.catalogCallError(run, id, call.error) });
|
|
6094
|
+
}
|
|
6095
|
+
return { ok: false, error: call.error as unknown as TJsonValue };
|
|
6096
|
+
}
|
|
6097
|
+
if (signal.aborted) return stopped(call.method);
|
|
6098
|
+
this.onToolStart(run.state, run.sessionId, {
|
|
6099
|
+
toolCallId: id, toolName: call.method, input: call.params, parentToolCallId: toolCallId,
|
|
6100
|
+
});
|
|
6101
|
+
const context = { method: call.method, toolCallId: id, abortSignal: signal, provider: providerFor(signal) };
|
|
6102
|
+
try {
|
|
6103
|
+
const decision = await call.decide(context);
|
|
6104
|
+
signal.throwIfAborted();
|
|
6105
|
+
if (decision === 'propose') {
|
|
6106
|
+
proposals.push({ id, method: call.method, params: call.params });
|
|
6107
|
+
const value = { proposed: true, id };
|
|
6108
|
+
finish(id, call.method, call.params, { output: value });
|
|
6109
|
+
return { ok: true, value };
|
|
6110
|
+
}
|
|
6111
|
+
if (decision !== 'run') {
|
|
6112
|
+
const payload = { error: 'denied' as const, method: call.method, reason: decision.deny };
|
|
6113
|
+
finish(id, call.method, call.params, { error: this.catalogCallError(run, id, payload) });
|
|
6114
|
+
return { ok: false, error: payload };
|
|
6115
|
+
}
|
|
6116
|
+
const value = normalizeJsonValue(await settleToolOutput(call.run(context)), this.toolOutputLimits);
|
|
6117
|
+
finish(id, call.method, call.params, { output: value });
|
|
6118
|
+
return { ok: true, value };
|
|
6119
|
+
} catch (error) {
|
|
6120
|
+
const projected = this.projectExternalError(run, error, 'toolExecution');
|
|
6121
|
+
finish(id, call.method, call.params, { error: projected });
|
|
6122
|
+
return { ok: false, error: { error: 'failed', method: call.method, message: projected.message } };
|
|
6123
|
+
}
|
|
6124
|
+
},
|
|
6125
|
+
};
|
|
6126
|
+
const result = await run.codeSandbox!.run(code, host, catalog.codeMode!, abortSignal);
|
|
6127
|
+
if (!result.ok) throw this.catalogCallError(run, toolCallId, result.failure);
|
|
6128
|
+
return {
|
|
6129
|
+
result: result.value,
|
|
6130
|
+
calls: result.calls,
|
|
6131
|
+
...(result.logs ? { logs: result.logs } : {}),
|
|
6132
|
+
...(proposals.length > 0
|
|
6133
|
+
? { proposals: await catalog.propose(proposals, { toolCallId, abortSignal, provider: base }) }
|
|
6134
|
+
: {}),
|
|
6001
6135
|
};
|
|
6002
6136
|
}
|
|
6003
6137
|
|
|
@@ -8505,7 +8639,7 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8505
8639
|
private onToolStart(
|
|
8506
8640
|
state: IStorageState,
|
|
8507
8641
|
sessionId: string,
|
|
8508
|
-
event: { toolCallId: string; toolName: string; input: unknown },
|
|
8642
|
+
event: { toolCallId: string; toolName: string; input: unknown; parentToolCallId?: string },
|
|
8509
8643
|
): void {
|
|
8510
8644
|
const run = this.callbackRun(state, sessionId);
|
|
8511
8645
|
if (!run || run.toolPartIds.has(event.toolCallId)) return;
|
|
@@ -8523,6 +8657,9 @@ export class FlexHarness<TScope = unknown> {
|
|
|
8523
8657
|
toolName: this.boundedIdentifier(target ? target.method : event.toolName, 'toolName'),
|
|
8524
8658
|
status: 'running',
|
|
8525
8659
|
input,
|
|
8660
|
+
...(event.parentToolCallId === undefined
|
|
8661
|
+
? {}
|
|
8662
|
+
: { parentToolCallId: this.boundedIdentifier(event.parentToolCallId, 'parentToolCallId') }),
|
|
8526
8663
|
startedAt: new Date().toISOString(),
|
|
8527
8664
|
};
|
|
8528
8665
|
run.toolPartIds.set(event.toolCallId, part.partId);
|