Welcome to Chapter 3!
In the previous chapter, Execution Context & State, we learned how the system hands our command a "briefcase" full of tools and data (the Context). We have the ingredients to cook our meal.
Now, we need to decide how to serve the meal. We can't just throw food on the table; we need a plate. In our system, that plate is the Standardized Result Object.
Imagine you are running a logistics company.
In programming, if one command returns a plain text string, another returns a number, and a third returns a complex JSON object, the core system will crash trying to figure out how to display them.
LocalCommandResult
We enforce a strict contract called LocalCommandResult. No matter what your command does, it must return an object that looks like this:
{
type: 'text', // The label on the box
value: 'Hello World' // The contents of the box
}
This is our "Shipping Container." The system sees this object and immediately knows: "Ah, this is text. I know how to print text to the user."
Every result object has at least two properties:
type: This tells the system how to interpret the data (e.g., 'text', 'image', 'error').value: This is the actual data you generated.
We use TypeScript to enforce this. If you try to return just a string like "Success!", the code won't compile. You must wrap it in the container.
Let's look at how we apply this to our files command. We want to list the files, but we need to wrap that list in our standardized object.
First, we tell TypeScript that we promise to use the shipping container format.
// Import the type definition
import type { LocalCommandResult } from '../../types/command.js'
// We promise that this function returns a LocalCommandResult
export async function call(
_args: string,
context: ToolUseContext
): Promise<LocalCommandResult> {
// ... logic
}
Explanation:
Promise<LocalCommandResult>: This is our pledge. We guarantee that when this function finishes, it will hand back a valid result object.Inside our function, once we have calculated our data (the list of files), we wrap it up.
// Assume we calculated this string earlier
const message = "Files in context: index.ts, style.css"
// Return the object
return {
type: 'text',
value: message
}
Explanation:
type: 'text': We explicitly say this is text.value: We put our calculated string here.What if there are no files? We still use the same container!
// If no files are found
return {
type: 'text',
value: 'No files in context'
}
Explanation:
Why do we do this? It allows the core system (the "Brain") to be dumb about the specific command but smart about the output.
When the system receives your object, it acts like a crane operator checking the manifest.
Here is a simplified look at the code that calls your command. It acts as a switchboard.
// simplified_runner.ts
async function runCommand(name, args, context) {
// 1. Run your command (we loaded this in Chapter 1)
const result = await command.call(args, context);
// 2. Handle the Shipping Container
switch (result.type) {
case 'text':
console.log("Output:", result.value);
break;
case 'error':
console.error("Command Failed:", result.value);
break;
// The system can easily be extended for new types!
}
}
Explanation:
result.type.files returns 'text', it enters the first case and prints the value.graph type, we wouldn't need to change the files command; we would just add a case here.In this chapter, we learned:
LocalCommandResult to promise the system a type and a value.We have now covered the Registration (Chapter 1), the Input (Chapter 2), and the Output (Chapter 3). We have defined the boundaries of our puzzle piece.
Now, it is finally time to fill in the middle. Let's write the actual code that finds the files!
Next Chapter: Command Implementation Logic
Generated by Code IQ