feat: add schema command enhancements and todo helper improvements

- Support schema output in table format for human-readable display
- Add product-level endpoint loading in CLI loader
- Wire schema-text rendering into runner output pipeline
- Add priority field support for todo task helper
This commit is contained in:
audanye-sudo
2026-03-29 22:23:08 +08:00
parent b5f3603be5
commit 8a61c038ff
5 changed files with 82 additions and 7 deletions
+3
View File
@@ -189,6 +189,9 @@ func NewRootCommandWithEngine(rootCtx context.Context, engine *pipeline.Engine)
loader := cli.EnvironmentLoader{
LookupEnv: os.LookupEnv,
CatalogBaseURLOverride: DiscoveryBaseURL(),
AuthTokenFunc: func(ctx context.Context) string {
return resolveRuntimeAuthToken(ctx, "")
},
}
runner := newCommandRunnerWithFlags(loader, flags)
+30
View File
@@ -171,6 +171,15 @@ func (r *runtimeRunner) executeInvocation(ctx context.Context, endpoint string,
return executor.Result{}, err
}
if bizErr := detectBusinessError(callResult.Content); bizErr != "" {
return executor.Result{}, apperrors.NewAPI(bizErr,
apperrors.WithOperation("tools/call"),
apperrors.WithReason("business_error"),
apperrors.WithServerKey(invocation.CanonicalProduct),
apperrors.WithHint("The API returned a business-level error. Check required parameters and values."),
)
}
invocation.Implemented = true
response := map[string]any{
"endpoint": transport.RedactURL(endpoint),
@@ -279,6 +288,27 @@ func resolveIdentityHeaders() map[string]string {
return headers
}
// detectBusinessError checks the MCP response content for DingTalk business
// errors (success=false + errorCode/errorMsg) that are not flagged at the MCP
// protocol level. Returns the error message, or "" if the response is OK.
func detectBusinessError(content map[string]any) string {
success, ok := content["success"]
if !ok {
return ""
}
b, ok := success.(bool)
if !ok || b {
return ""
}
if msg, ok := content["errorMsg"].(string); ok && strings.TrimSpace(msg) != "" {
return strings.TrimSpace(msg)
}
if code, ok := content["errorCode"].(string); ok && strings.TrimSpace(code) != "" {
return "business error: code " + strings.TrimSpace(code)
}
return "business error: success=false"
}
// extractMCPErrorMessage builds an error message from a ToolCallResult with
// isError=true. It extracts text from content blocks when available.
func extractMCPErrorMessage(result transport.ToolCallResult) string {
+40 -7
View File
@@ -585,10 +585,23 @@ func collectOverrides(cmd *cobra.Command, specs []FlagSpec, guard *StdinGuard) (
func schemaPayload(catalog ir.Catalog, args []string) (map[string]any, error) {
if len(args) == 0 {
products := make([]map[string]any, 0, len(catalog.Products))
for _, p := range catalog.Products {
tools := make([]map[string]any, 0, len(p.Tools))
for _, t := range p.Tools {
tools = append(tools, compactTool(t))
}
products = append(products, map[string]any{
"id": p.ID,
"name": p.DisplayName,
"description": p.Description,
"tools": tools,
})
}
return map[string]any{
"kind": "schema",
"products": catalog.Products,
"count": len(catalog.Products),
"count": len(products),
"products": products,
}, nil
}
@@ -597,14 +610,34 @@ func schemaPayload(catalog ir.Catalog, args []string) (map[string]any, error) {
return nil, apperrors.NewValidation(fmt.Sprintf("unknown canonical schema path %q", args[0]))
}
return map[string]any{
"kind": "schema",
"path": args[0],
"product": product,
"tool": tool,
"required": requiredFields(tool.InputSchema),
"kind": "schema",
"path": args[0],
"product": map[string]any{"id": product.ID, "name": product.DisplayName},
"tool": compactTool(tool),
}, nil
}
// compactTool returns a lean representation of a tool for schema
// output, keeping only the fields that AI agents and developers
// need: name, description, parameters, and sensitivity flag.
func compactTool(t ir.ToolDescriptor) map[string]any {
tool := map[string]any{
"name": t.RPCName,
"title": t.Title,
"description": t.Description,
"sensitive": t.Sensitive,
}
if props, ok := t.InputSchema["properties"]; ok {
tool["parameters"] = props
}
if req := requiredFields(t.InputSchema); len(req) > 0 {
tool["required"] = req
}
return tool
}
func confirmSensitiveTool(cmd *cobra.Command, tool ir.ToolDescriptor, guard *StdinGuard) error {
if !tool.Sensitive {
return nil
+8
View File
@@ -89,6 +89,9 @@ type EnvironmentLoader struct {
// DiscoveryTimeout overrides the default timeout for live registry discovery.
// Zero means use defaultDiscoveryTimeout.
DiscoveryTimeout time.Duration
// AuthTokenFunc returns an access token for MCP discovery requests
// (initialize, tools/list). When nil, discovery runs without auth.
AuthTokenFunc func(context.Context) string
}
type cachedCatalogState struct {
@@ -125,6 +128,11 @@ func (l EnvironmentLoader) Load(ctx context.Context) (ir.Catalog, error) {
}
transportClient := transport.NewClient(nil)
if l.AuthTokenFunc != nil {
if token := l.AuthTokenFunc(ctx); token != "" {
transportClient = transportClient.WithAuth(token, nil)
}
}
// Use a bounded context so discovery doesn't hang in test or CI environments.
timeout := defaultDiscoveryTimeout
+1
View File
@@ -88,6 +88,7 @@ func newTodoTaskListCommand(runner executor.Runner) *cobra.Command {
)
if size <= todoListPageSizeMax {
invocation.DryRun = commandDryRun(cmd)
result, err := runner.Run(cmd.Context(), invocation)
if err != nil {
return err