Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
528da7cc31 |
No files matched your search
@@ -95,6 +95,46 @@ const firstAnalysisCancellation = <Value>(
|
|||||||
* Direct callers use `execute`; adapters that own validation use
|
* Direct callers use `execute`; adapters that own validation use
|
||||||
* `executeValidated` so an MCP input is parsed exactly once.
|
* `executeValidated` so an MCP input is parsed exactly once.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const NO_PSEUDOCODE =
|
||||||
|
"The provider reports no pseudocode for this procedure; the batch limitations for procedure_pseudo_code say why.";
|
||||||
|
|
||||||
|
/** One batch item from its pseudocode observation, keeping null distinct from unreadable output. */
|
||||||
|
const batchPseudocodeItem = (
|
||||||
|
address: string,
|
||||||
|
procedure: BatchProcedureIdentity,
|
||||||
|
observed: Result<AnalysisExecution, AnalysisError>,
|
||||||
|
): BatchDecompileItem => {
|
||||||
|
const failed = (error: AnalysisError): BatchDecompileItem => ({
|
||||||
|
address,
|
||||||
|
procedure,
|
||||||
|
status: "error",
|
||||||
|
error: projectAnalysisError(error),
|
||||||
|
});
|
||||||
|
if (!observed.ok) return failed(observed.error);
|
||||||
|
const pseudocode = observed.value.result;
|
||||||
|
// The contract's null is the provider's own answer that this procedure has
|
||||||
|
// no pseudocode (an import slot, a failed body); the provider's reason is
|
||||||
|
// in the batch limitations, so it is not reported as unreadable output.
|
||||||
|
if (pseudocode === null)
|
||||||
|
return failed(
|
||||||
|
new AnalysisCapabilityUnavailableError(
|
||||||
|
observed.value.provider.id,
|
||||||
|
"procedure_pseudo_code",
|
||||||
|
NO_PSEUDOCODE,
|
||||||
|
{ userMessage: NO_PSEUDOCODE },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (typeof pseudocode !== "string" || pseudocode.length === 0)
|
||||||
|
return failed(
|
||||||
|
new AnalysisOutputError(
|
||||||
|
"procedure_pseudo_code",
|
||||||
|
"provider returned empty pseudocode",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return { address, procedure, status: "ok", pseudocode };
|
||||||
|
};
|
||||||
|
|
||||||
export class EnhancedTools {
|
export class EnhancedTools {
|
||||||
constructor(private readonly analysis: AnalysisOperationPort) {}
|
constructor(private readonly analysis: AnalysisOperationPort) {}
|
||||||
|
|
||||||
@@ -375,7 +415,7 @@ export class EnhancedTools {
|
|||||||
): Promise<Result<BatchDecompileItem, AnalysisCancelledError>[]> {
|
): Promise<Result<BatchDecompileItem, AnalysisCancelledError>[]> {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
ready.map(async ({ address, procedure, providerProcedure }) => {
|
ready.map(async ({ address, procedure, providerProcedure }) => {
|
||||||
const result = await this.#call(
|
const result = await this.#observe(
|
||||||
"procedure_pseudo_code",
|
"procedure_pseudo_code",
|
||||||
{ procedure: providerProcedure },
|
{ procedure: providerProcedure },
|
||||||
signal,
|
signal,
|
||||||
@@ -383,32 +423,7 @@ export class EnhancedTools {
|
|||||||
);
|
);
|
||||||
if (!result.ok && isAnalysisCancellation(result.error))
|
if (!result.ok && isAnalysisCancellation(result.error))
|
||||||
return err(result.error);
|
return err(result.error);
|
||||||
const item: BatchDecompileItem = !result.ok
|
return ok(batchPseudocodeItem(address, procedure, result));
|
||||||
? {
|
|
||||||
address,
|
|
||||||
procedure,
|
|
||||||
status: "error",
|
|
||||||
error: projectAnalysisError(result.error),
|
|
||||||
}
|
|
||||||
: typeof result.value !== "string" || result.value.length === 0
|
|
||||||
? {
|
|
||||||
address,
|
|
||||||
procedure,
|
|
||||||
status: "error",
|
|
||||||
error: projectAnalysisError(
|
|
||||||
new AnalysisOutputError(
|
|
||||||
"procedure_pseudo_code",
|
|
||||||
"provider returned empty pseudocode",
|
|
||||||
),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
address,
|
|
||||||
procedure,
|
|
||||||
status: "ok",
|
|
||||||
pseudocode: result.value,
|
|
||||||
};
|
|
||||||
return ok(item);
|
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -470,6 +470,47 @@ describe("enhanced MCP batches", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reports a provider's null pseudocode as unavailable, not unreadable", async () => {
|
||||||
|
const client = await connect({
|
||||||
|
execute: async (name, arguments_) =>
|
||||||
|
name === "procedure_address"
|
||||||
|
? ok(String(arguments_.procedure))
|
||||||
|
: name === "address_name"
|
||||||
|
? ok(null)
|
||||||
|
: ok(arguments_.procedure === "0x1" ? null : ""),
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await client.callTool({
|
||||||
|
name: "batch_decompile",
|
||||||
|
arguments: { addresses: ["0x1", "0x2"] },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.isError).not.toBe(true);
|
||||||
|
expect(jsonResult(result)).toMatchObject({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
address: "0x1",
|
||||||
|
status: "error",
|
||||||
|
error: {
|
||||||
|
code: "capability_unavailable",
|
||||||
|
retryable: false,
|
||||||
|
message:
|
||||||
|
"The provider reports no pseudocode for this procedure; the batch limitations for procedure_pseudo_code say why.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// An empty string is still output no provider should produce.
|
||||||
|
{
|
||||||
|
address: "0x2",
|
||||||
|
status: "error",
|
||||||
|
error: { code: "unreadable_output" },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
total: 2,
|
||||||
|
succeeded: 0,
|
||||||
|
failed: 2,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("returns ordered typed batch failures and zero counts for empty input", async () => {
|
it("returns ordered typed batch failures and zero counts for empty input", async () => {
|
||||||
const client = await connect({
|
const client = await connect({
|
||||||
execute: async (name, arguments_) =>
|
execute: async (name, arguments_) =>
|
||||||
|
|||||||
Reference in new issue
Block a user